xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

多次元配列を LINQ で簡単に扱おう

コレクション操作と言ったら LINQ!もはやコレがないと今の時代のプログラマとしてご飯を食べていけなくなるくらい、ないと困る存在であり強い味方です。

以前インデックス付きで簡単にアクセスする方法として、Indexed 拡張メソッドを作ってしまおうというのを紹介しました。今だと WithIndex というメソッド名が主流なのかな?

以下のような感じですね。

//--- 構造体でラップ
public struct IndexedItem<T>
{
    public T Element { get; }
    public int Index { get; }
    public IndexedItem(T element, int index)
    {
        this.Element = element;
        this.Index = index;
    }
}

//--- 拡張メソッド
public static IEnumerable<IndexedItem<T>> WithIndex<T>(this IEnumerable<T> self)
{
    if (self == null)
        throw new ArgumentNullException(nameof(self));
    return self.Select((x, i) => new IndexedItem<T>(x, i));
}

多次元配列に対して適用する

では 1 次元増やして 2 次元になるとどうでしょうか?以下のように書けば真っ直ぐ並べることができます。

//--- こんなテキトーな 2 次元配列があるとする
var cells = new int[,]
{
    { 0, 0 }, { 0, 1 },
    { 1, 0 }, { 1, 1 },
};

//--- Cast メソッドで 1 次元に落とし込む
var collection = cells.Cast<int>();

//--- foreach でそのまま扱ってもいいよ
foreach (var value in cells)
{
    //--- value は
    //--- [ 0, 0, 0, 1, 1, 0, 1, 1 ]
    //--- の順に取れる
}

要素にアクセスしたいだけであればこれで良いのですが、(x, y) のインデックスは取得できません。そこで以下のようにしてしまいましょう。

//--- 2 次元配列用に入れ物を用意する
public struct IndexedItem2<T>
{
    public T Element { get; }
    public int X { get; }
    public int Y { get; }
    internal IndexedItem2(T element, int x, int y)
    {
        this.Element = element;
        this.X = x;
        this.Y = y;
    }
}

//--- 拡張メソッド
public static IEnumerable<IndexedItem2<T>> WithIndex<T>(this T[,] self)
{
    if (self == null)
        throw new ArgumentNullException(nameof(self));

    for (int x = 0; x < self.GetLength(0); x++)
    for (int y = 0; y < self.GetLength(1); y++)
        yield return new IndexedItem2<T>(self[x, y], x, y);
}

これで以下のように簡単にインデックス付きの要素アクセスができるようになります。

//--- こんな 2 重ループが...
for (int x = 0; x < self.GetLength(0); x++)
for (int y = 0; y < self.GetLength(1); y++)
{
    var element = cells[x, y];
}

//--- こう書ける!(そりゃそーだ
foreach (var item in cells.WithIndex())
{
    var element = item.Element;
    var x = item.X;
    var y = item.Y;
}

たったこれだけで多重ループが綺麗サッパリなくなります。しかしそれ以上に SelectWhere などの通常の LINQ のメソッドが使えるようになるのが大きなメリットです。

必要に応じて 3 次元以上のものも用意しておきましょう。これで多重ループも怖くないですね!