Edited at

2次元配列でLINQしたい

2次元配列を扱うコードを書いてたら、行や列に対してLINQが使えないのが微妙にストレスだったんで、簡単な拡張メソッドを書いてみました。


/// <summary>
/// 2次元方向
/// </summary>
public enum SquareDirection { Row = 0, Column = 1 }

/// <summary>
/// 配列の拡張メソッド
/// </summary>
public static class ArrayEx
{
/// <summary>
/// 2次元配列向け列クラス
/// </summary>
/// <typeparam name="T"></typeparam>
public class SquareArrayColumn<T> : IEnumerable<T>
{
/// <summary>
/// 2次元配列
/// </summary>
private T[,] _array;
/// <summary>
/// 列インデックス
/// </summary>
private int _colIndex;

/// <summary>
/// インデクサ
/// </summary>
/// <param name="rowIndex">行インデックス</param>
/// <returns>要素</returns>
public T this[int rowIndex]
{
get => _array[rowIndex, _colIndex];
set => _array[rowIndex, _colIndex] = value;
}

/// <summary>
/// Length
/// </summary>
public int Length => _array.GetLength(0);

/// <summary>
/// Constructor
/// </summary>
/// <param name="array">2次元配列</param>
/// <param name="rowIndex">行インデックス</param>
public SquareArrayColumn(T[,] array, int colIndex)
{
_array = array;
_colIndex = colIndex;
}

/// <summary>
/// GetEnumerator()の実装
/// </summary>
/// <returns>IEnumerator<T></returns>
public IEnumerator<T> GetEnumerator()
{
for (var i = 0; i < Length; i++)
yield return this[i];
}

/// <summary>
/// GetEnumerator()の実装
/// </summary>
/// <returns>IEnumerator</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}

/// <summary>
/// 2次元配列向け行クラス
/// </summary>
/// <typeparam name="T">型パラメータ</typeparam>
public class SquareArrayRow<T> : IEnumerable<T>
{
/// <summary>
/// 2次元配列
/// </summary>
private T[,] _array;
/// <summary>
/// 行インデックス
/// </summary>
private int _rowIndex;

/// <summary>
/// インデクサ
/// </summary>
/// <param name="colIndex">列インデックス</param>
/// <returns>要素</returns>
public T this[int colIndex]
{
get => _array[_rowIndex, colIndex];
set => _array[_rowIndex, colIndex] = value;
}

/// <summary>
/// Length
/// </summary>
public int Length => _array.GetLength(1);

/// <summary>
/// Constructor
/// </summary>
/// <param name="array">2次元配列</param>
/// <param name="rowIndex">行インデックス</param>
public SquareArrayRow(T[,] array, int rowIndex)
{
_array = array;
_rowIndex = rowIndex;
}

/// <summary>
/// GetEnumerator()の実装
/// </summary>
/// <returns>IEnumerator<T></returns>
public IEnumerator<T> GetEnumerator()
{
for (var i = 0; i < Length; i++)
yield return this[i];
}

/// <summary>
/// GetEnumerator()の実装
/// </summary>
/// <returns>IEnumerator</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}

/// <summary>
/// 2次元配列から行を取得する
/// </summary>
/// <typeparam name="T">型パラメータ</typeparam>
/// <param name="array">this 2次元配列</param>
/// <param name="rowIndex">行インデックス</param>
/// <returns>行オブジェクト</returns>
public static SquareArrayRow<T> Rows<T>(this T[,] array, int rowIndex)
{
if (rowIndex < 0 || array.GetLength(0) <= rowIndex)
throw new IndexOutOfRangeException();
return new SquareArrayRow<T>(array, rowIndex);
}

/// <summary>
/// 2次元配列から行を列挙する
/// </summary>
/// <typeparam name="T">型パラメータ</typeparam>
/// <param name="array">this 2次元配列</param>
/// <returns>行オブジェクトの列挙子</returns>
public static IEnumerable<SquareArrayRow<T>> Rows<T>(this T[,] array)
{
for (var row = 0; row < array.GetLength(0); row++)
yield return array.Rows(row);
}

/// <summary>
/// 2次元配列から列を取得する
/// </summary>
/// <typeparam name="T">型パラメータ</typeparam>
/// <param name="array">this 2次元配列</param>
/// <param name="colIndex">列インデックス</param>
/// <returns>行オブジェクト</returns>
public static SquareArrayColumn<T> Cols<T>(this T[,] array, int colIndex)
{
if (colIndex < 0 || array.GetLength(1) <= colIndex)
throw new IndexOutOfRangeException();
return new SquareArrayColumn<T>(array, colIndex);
}

/// <summary>
/// 2次元配列から列を列挙する
/// </summary>
/// <typeparam name="T">型パラメータ</typeparam>
/// <param name="array">this 2次元配列</param>
/// <returns>行オブジェクトの列挙子</returns>
public static IEnumerable<SquareArrayColumn<T>> Cols<T>(this T[,] array)
{
for (var col = 0; col < array.GetLength(1); col++)
yield return array.Cols(col);
}

/// <summary>
/// 2次元配列をまとめて列挙する
/// </summary>
/// <typeparam name="T">型パラメータ</typeparam>
/// <param name="array">2次元配列</param>
/// <param name="direction">方向</param>
/// <returns>列挙子</returns>
public static IEnumerable<T> Flatten<T>(this T[,] array, SquareDirection direction = SquareDirection.Row)
{
IEnumerable<T> rowDirection()
{
for (var row = 0; row < array.GetLength(0); row++)
{
for (var col = 0; col < array.GetLength(1); col++)
{
yield return array[row, col];
}
}
}
IEnumerable<T> colDirection()
{
for (var col = 0; col < array.GetLength(1); col++)
{
for (var row = 0; row < array.GetLength(0); row++)
{
yield return array[row, col];
}
}
}

if (direction == SquareDirection.Row)
return rowDirection();
else
return colDirection();
}
}

使い方は、拡張メソッドなので任意の2次元配列 (array<T>[行,列]) に対して、

array.Rows()

で行単位での Enumerator、

array.Cols()

で列単位の Enumerator を返します。

例えば、2次元配列の行単位の合計を取得するには

var array = new int[,]

{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};

// 行毎の合計を撮る
var sums = array.Rows.Select(r => r.Sum()).ToArray(); // => [ 6, 15, 24 ]

という感じ。