今回は
「ネストが深くならない」
「多重forループからbreakできる」
という2つの機能を持つfor文について考えてみます。
for文はいかなるプログラムでもついて回り、次元が増えれば増えるほどネストします。
例えば3重ループの処理なら
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
for (int k = 0; k < 10; k++)
{
// TODO
}
}
}
このように記述すると思いますが、何かしらの処理を記述するころにはネストがかなり深くなってしまっています。
多次元配列ならばforeachでネストせずに記述できますが、
int[][]
のようなジャグ配列やListでは結局ネストせざる終えません。
そんな多重ループの回避法としては途中で関数をかませて別の場所に記述すれば良いですが、
そうしたくない時もあると思います。
作るもの
そんなわけで今回作るのはネストしない多重forループ関数です。
作った関数は以下の様に利用できます。
var array = new int[5][][]; // 3次元ジャグ配列
// arrayの配列要素を生成
For(0, 5, 0, 5, (i, j) =>
{
if (j == 0) array[i] = new int[5][];
array[i][j] = new int[5];
});
// arrayの要素を初期化
For(0, 5, 0, 5, 0, 5, (i, j, k) =>
{
// i + j + k が奇数なら-1, 偶数なら1をセット
array[i][j][k] = ((i + j + k) % 2) == 0 ? 1 : -1;
});
Forという関数を使うことで、3重ループをネストせずに回すことができます。
第一引数はループの開始index、第二引数はループのindexの上限
後はそれがループの数だけあって、その後に処理したい内容を記述します。
実装
結構単純なトリックです。
以下の拡張メソッドを実装するだけです。
using System;
public static void For(int s1, int m1, int s2, int m2, Action<int, int> action)
{
for (int i = s1; i < m1; i++)
{
for (int j = s2; j < m2; j++)
{
action(i, j);
}
}
}
public static void For(int s1, int m1, int s2, int m2, Funk<int, int, bool> action)
{
for (int i = s1; i < m1; i++)
{
for (int j = s2; j < m2; j++)
{
if(!action(i, j)) return;
}
}
}
純粋に別の場所で多重ループを回して、Actionを実行しているだけです。
そして、参照場所がわかるようにactionにiとjを渡しています。
上記は2重ループ用の関数しか定義していませんが、3重ループも同じ様にオーバーロードすれば作成できます。
ちょこっと工夫してあるのは、多重ループをすぐbreakできる2つ目の様なFor関数です。
普通、多重forループは抜け出すためにはbreak用のflagを定義するかgoto(NG行為)を使わなければならないのでちょっと厄介ですが、このFor関数を使うと
For(0, 5, 0, 5, (i, j) =>
{
// 何かしらの処理
return i * j < 50;
});
breakしたい時にfalseを返せば抜けることができます。上記の例だと i * j
が50を超えたときに多重ループをすぐbreakできます。
多重ループを抜けるためだけのフラグ変数を使わなくて良いので、見た目もすっきりします。
まとめ
以上、多重forループ処理をネストさせず、かつbreakも可能なFor関数の実装でした。
ただ、for文というプログラムの根幹とも言える処理を置き換えているため、多用は厳禁です。
なのでこの様な考え方もあるという参考程度に留めていただければ幸いです。