目的
無限IEnumerableについて紹介し、いくつか簡単な例を挙げます。
無限IEnumerable
C#(ほか様々な言語)では、無限に列挙し続けるイテレータを書くことが出来ます。
例えば、以下のOnesは無限に続く1を表します。
public static IEnumerable<int> Ones() {
while (true) yield return 1;
}
また、以下のNatは0から始まる自然数全体を昇順で列挙します。
public static IEnumerable<int> Nat() {
int value = 0;
while (true) yield return value++;
}
実際に以下のように出力してみると、列挙が止まらないことが確認できます。
foreach (var n in Nat()) Console.WriteLine(n);
出力結果は 0 1 2 3 4 5 6 7 8 9 10 11 ... となるはずです。
無限IEnumerableの使い方
無限に実行が止まらないのではなかなか使いみちがありませんので、通常はTakeやTakeWhileなどと組み合わせて使うことが多いかと思います。
例えば、先ほどのNatであれば、
foreach (var n in Nat().Take(10)) Console.WriteLine(n);
などとすると、 0 1 2 3 4 5 6 7 8 9 の10個の自然数を表示することができます。
無限IEnumerableの例
- 正整数を列挙
public static IEnumerable<int> Pos() => Nat().Skip(1);
- 0以上の偶数を列挙
public static IEnumerable<int> Evens() => Nat().Where(x => x % 2 == 0);
- 0以上のnの倍数を列挙
public static IEnumerable<int> Multiples(int n) => Nat().Select(x => x * n);
- FizzBuzz
public static IEnumerable<string> FizzBuzz() => Pos().Select(
x =>
x % 15 == 0 ? "FizzBuzz"
: x % 3 == 0 ? "Fizz"
: x % 5 == 0 ? "Buzz"
: x.ToString());
updateを補助する関数
前の値一つを使って次の値が決定されるような列挙については、以下のような補助関数Iterateを定義すると書きやすくなります。
public static IEnumerable<T> Iterate<T>(Func<T, T> updater, T init) {
while (true) {
yield return init;
init = updater(init);
}
}
たとえば、先ほどのNatは以下のように書けます。
public static IEnumerable<int> Nat() => Iterate(x => x + 1, 0);
最初の定義と比べると見た目がシンプルになりました。
また、1, 10, 100, .... を列挙するOneZerosは以下のように書けます。
public static IEnumerable<int> OneZeros() => Iterate(x => x * 10, 1);
nから始まるCollatz列は以下のように書けます。
public static IEnumerable<int> Collatz(int n) => Iterate(x => x % 2 == 0 ? x / 2 : x * 3 + 1, n);
結論
無理に有限に制限するインターフェースにせずとも、無限に列挙するイテレータ+Takeなどで素朴に書ける場面がたまにあります。
参考になれば幸いです。