IEnumerable, IEnumerator

GetEnumerator() を定義すると、foreach でループを回すことができます。

using System;
using System.Collections;
using System.Collections.Generic;

class Program {
    static IEnumerable<int> G() {
        yield return 1;
        yield return 2;
    }

    static IEnumerator<int> T() {
        yield return 3;
        yield return 4;
    }

    static void Main() {
        // コンパイルエラー:
        //     error CS1579: foreach statement cannot operate on variables of type
        //     `System.Collections.Generic.IEnumerator<int>'
        //     because it does not contain a definition for `GetEnumerator'
        //     or is inaccessible
        // foreach (var a in T())
        //     Console.WriteLine(a);

        var g = G().GetEnumerator();
        while (g.MoveNext()) {
            Console.WriteLine(g.Current);
        }

        var t = T();
        while (t.MoveNext()) {
            Console.WriteLine(t.Current);
        }
    }
}

実行結果です。

1
2
3
4

プロパティでコルーチン

プロパティでコルーチンを定義するサンプルです。

using System;
using System.Collections.Generic;
using System.Linq;

class Test {
    public IEnumerable<int> G {
        get {
            int n = 0;
            while (true) {
                yield return n++;
            }
        }
    }
}

class Program {
    static void Main() {
        Console.WriteLine(string.Join(" ", new Test().G.Take(5)));
        //=> 0 1 2 3 4
    }
}

実行結果です。

0 1 2 3 4

速度のために List クラスに Last 拡張メソッドを定義しようとしたけど要らなかった

やろうとしたこと

  • List<T> の最後の要素を取得するメソッドが欲しい
  • Enumerable.Last メソッドがある
    • だけど、シーケンスを辿るので O(N) で遅いのでは?

先に結論から言うと、IList<T> インターフェース を実装しているなら、 Enumerable.Last は O(N) ではなく O(1) になります。後ほど、Enumerable.Last の実装をみます。

List.MyLast 拡張メソッドを定義して、速度を計測してみた

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

public static class Extension {
    public static T MyLast<T>(this List<T> list) {
        return list[list.Count - 1];
    }
}

class Program {
    static void TestLast(int n, int m) {
        var xs = Enumerable.Range(0, m).ToList();
        var sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < n; i++) {
            xs.Last();
        }
        sw.Stop();
        Console.WriteLine("Last  : n:{0} time:{1}", n, sw.ElapsedMilliseconds);
    }

    static void TestMyLast(int n, int m) {
        var xs = Enumerable.Range(0, m).ToList();
        var sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < n; i++) {
            xs.MyLast();
        }
        sw.Stop();
        Console.WriteLine("MyLast: n:{0} time:{1}", n, sw.ElapsedMilliseconds);
    }

    static void Main() {
        int size = 1000000;
        foreach (var x in new[] { 1000000, 10000000 }) {
            TestLast(x, size);
            TestMyLast(x, size);
        }
    }
}

実行結果です。

Last  : n:1000000 time:13
MyLast: n:1000000 time:5
Last  : n:10000000 time:117
MyLast: n:10000000 time:45

2 倍程度しか差がありません。

Enumerable.Last の実装をみてみる

Reference Source の Enumerable.Last の実装 をみてみると、as 演算子IList<T> インターフェース を実装しているか確認しています。もし、IList<T> インターフェースを実装しているなら、インデックスによるアクセスで最後の要素を取得しています。

まとめ

タプルを == 演算子で比較、Equals() で比較

using System;

class Program {
    static void Main() {
        var a = Tuple.Create(1, 2);
        var b = Tuple.Create(1, 2);

        Console.WriteLine(a == b);      // False
        Console.WriteLine(a.Equals(b)); // True
    }
}

実行結果です。

False
True

タプルを Dictionary のキーにしてみる

using System;
using System.Collections.Generic;

class Program {
    static void Main() {
        var a = Tuple.Create(1, 2);
        var b = Tuple.Create(1, 2);

        var dict = new Dictionary<Tuple<int, int>, int>();
        dict[a] = 12;
        Console.WriteLine(dict[a]); // 12
        dict[b] = 30;
        Console.WriteLine(dict[a]); // 30
        Console.WriteLine(dict[b]); // 30
    }
}

実行結果です。

12
30
30

タプルを HashSet に格納

using System;
using System.Collections.Generic;

class Program {
    static void Main() {
        var a = Tuple.Create(1, 2);
        var b = Tuple.Create(1, 2);

        var hs = new HashSet<Tuple<int, int>>();
        hs.Add(a);
        Console.WriteLine(hs.Contains(b)); // True
    }
}

実行結果です。

True

2 つの Dictionary をひとつにまとめる

両方の Dictionary に同じキーが含まれる場合は、Sum() で足し合わせます。

using System;
using System.Collections.Generic;
using System.Linq;

class Program {
    static Dictionary<int, int> Add(Dictionary<int, int> a, Dictionary<int, int> b) {
        return a.Concat(b).GroupBy(e => e.Key, e => e.Value).ToDictionary(e => e.Key, e => e.Sum());
    }

    static void Main() {
        var a = new Dictionary<int, int> {
            { 1, 2 },
            { 2, 3 },
        };

        var b = new Dictionary<int, int> {
            { 0, 1 },
            { 1, 1 },
            { 2, 2 },
            { 5, 0 },
        };

        var c = Add(a, b);
        foreach (var k in c.Keys.OrderBy(e => e)) {
            Console.WriteLine("key:{0} val:{1}", k, c[k]);
        }
    }
}

実行結果です。

key:0 val:1
key:1 val:3
key:2 val:5
key:5 val:0

0,1,0,1,0,1... を繰り返すシーケンス

using System;
using System.Collections.Generic;
using System.Linq;

class Program {
    static IEnumerable<int> ZeroOne() {
        int n = 0;
        while (true) {
            yield return n;
            n ^= 1;
        }
    }

    static void Main() {
        Console.WriteLine(string.Join(",", ZeroOne().Take(10)));
        // => 0,1,0,1,0,1,0,1,0,1
    }
}

実行結果です。

0,1,0,1,0,1,0,1,0,1

Pairs 関数

Python

>>> [(i, j) for i in range(3) for j in range(3)]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]

のような各ペアを求める Pairs 関数を作成してみました。

using System;
using System.Collections.Generic;
using System.Linq;

public static class Iter {
    static IEnumerable<T[]> Product_1<T>(int p, T[] buf, List<List<T>> pools) {
        if (p == buf.Length) {
            yield return buf;
        }
        else {
            foreach (var e in pools[p]) {
                buf[p] = e;
                foreach (var ret in Product_1(p+1, buf, pools)) {
                    yield return ret;
                }
            }
        }
    }

    public static IEnumerable<T[]> Product<T>(IEnumerable<T> xs, int repeat = 1) {
        var pools = new List<List<T>>(Enumerable.Repeat(xs.ToList(), repeat));
        return Product_1(0, new T[pools.Count], pools);
    }

    public static IEnumerable<T[]> Product<T>(IEnumerable<T> xs, IEnumerable<T> ys, int repeat = 1) {
        var ls = new List<List<T>> { xs.ToList(), ys.ToList() };
        var pools = new List<List<T>>(Enumerable.Repeat(ls, repeat).SelectMany(e => e));
        return Product_1(0, new T[pools.Count], pools);
    }

    public static IEnumerable<U> Pairs<T, U>(IEnumerable<T> xs, IEnumerable<T> ys, Func<T, T, U> fn) {
        foreach (var e in Product(xs, ys)) {
            var cs = e.ToArray();
            yield return fn(cs[0], cs[1]);
        }
    }
}

class Program {
    static void Main() {
        var xs = new[] { 0, 1, 2 };
        var ys = Iter.Pairs(xs, xs, (a, b) => Tuple.Create(a, b));
        Console.WriteLine(string.Join(" ", ys));
        // => (0, 0) (0, 1) (0, 2) (1, 0) (1, 1) (1, 2) (2, 0) (2, 1) (2, 2)
    }
}

Product を経由せずに、foreach で 2 重ループを回すほうが簡単ですが、Product の応用を考えていて Pairs を作れることが分かったので作ってみました。

リンク