読者です 読者をやめる 読者になる 読者になる

文字列の配列を文字のシーケンスに変換する

using System;
using System.Linq;

class Program {
    static void Main() {
        var ss = new[] { "foo", "bar", "baz" };

        // 文字列の配列を文字のシーケンスにする
        var cs = ss.SelectMany(e => e);
        Console.WriteLine(string.Join(" ", cs));
        // => f o o b a r b a z

        // 文字列を反転してから繋げる
        var cs2 = ss.SelectMany(e => e.Reverse());
        Console.WriteLine(string.Join(" ", cs2));
        // => o o f r a b z a b

        // 特定の文字を削除してから繋げる('a' を取り除く)
        var cs3 = ss.SelectMany(e => e.Where(c => c != 'a'));
        Console.WriteLine(string.Join(" ", cs3));
        // => f o o b r b z
    }
}

実行結果です。

f o o b a r b a z
o o f r a b z a b
f o o b r b z

リンク

Frequencies メソッドを作る

シーケンスに含まれる要素の出現回数を求める Frequencies メソッドを作ってみました。

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

static class Ext {
    public static Dictionary<T, int> Frequencies<T>(this IEnumerable<T> xs) {
        var d = new Dictionary<T, int>();
        foreach (var x in xs) {
            if (!d.ContainsKey(x))
                d[x] = 1;
            else
                d[x]++;
        }
        return d;
    }
}

class Program {
    static void Test<T>(T[] xs) {
        var d = xs.Frequencies().Select(e => $"{e.Key} {e.Value}");
        Console.WriteLine(string.Join(", ", d));
    }

    static void Main() {
        Test(new[] { 1, 2, 3, 1, 1, 2, 2 });
        // => 1 3, 2 3, 3 1

        Test(new[] { "foo", "bar", "foo", "buz" });
        // => foo 2, bar 1, buz 1

        Test(new[] { Tuple.Create(1, 2), Tuple.Create(1, 2), Tuple.Create(2, 3) });
        // => (1, 2) 2, (2, 3) 1
    }
}

実行結果です。

1 3, 2 3, 3 1
foo 2, bar 1, buz 1
(1, 2) 2, (2, 3) 1

CompositeDisposable クラスの Dispose メソッドを呼び出した後に Add したときの振る舞い

  • CompositeDisposable クラスの Dispose メソッドを呼び出すと、CompositeDisposable に格納されている全ての要素に対して Dispose メソッドが呼ばれます。
  • CompositeDisposable クラスの Dispose メソッドを呼び出すと、IsDisposed プロパティは true となり、Dispose 済みとなります。
    • それ以降は Add メソッドで要素を追加すると、Add した直後にその要素の Dispose メソッドが呼ばれます。

CompositeDisposable を使い回そうとしてはまりました。

using System;
using System.Reactive.Disposables;

class Foo : IDisposable {
    public int Id { get; private set; }

    public Foo(int id) {
        Id = id;
    }

    public void Dispose() {
        Console.WriteLine($"id:{Id} dispose");
    }
}

class Program {
    static void Main(string[] args) {
        var d = new CompositeDisposable();
        d.Add(new Foo(1));
        d.Add(new Foo(2));

        Console.WriteLine("IsDisposed: {0}", d.IsDisposed); // false
        Console.WriteLine("-- call dispose --");
        d.Dispose();
        Console.WriteLine("IsDisposed: {0}", d.IsDisposed); // true

        Console.WriteLine("-- Add --");
        d.Add(new Foo(3)); // ただちに Foo.Dispose が呼ばれる

        Console.WriteLine("-- call dispose --");
        d.Dispose();
    }
}

コンパイル、実行結果です。

% mcs composite-disposable.cs -r:System.Reactive.Core.dll,System.Reactive.Interfaces.dll -out:a.exe
% mono a.exe
IsDisposed: False
-- call dispose --
id:1 dispose
id:2 dispose
IsDisposed: True
-- Add --
id:3 dispose
-- call dispose --

リンク

Butlast メソッド

シーケンスの最後の要素を取り除く Butlast メソッドを作ってみました。

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

public static class Ext {
    public static IEnumerable<T> Butlast<T>(this IEnumerable<T> xs) {
        bool first = true;
        T prev = default(T);
        foreach (var x in xs) {
            if (!first) {
                yield return prev;
            }
            prev = x;
            first = false;
        }
    }
}

class Program {
    static void Display<T>(IEnumerable<T> xs) {
        Console.WriteLine("count:{0} [{1}]", xs.Count(), string.Join(" ", xs));
    }

    static void Main() {
        Display(new[] { 1, 2, 3 }.Butlast()); // count:2 [1 2]
        Display(new[] { 1, 2 }.Butlast());    // count:1 [1]
        Display(new[] { 1 }.Butlast());       // count:0 []
        Display(Enumerable.Empty<int>().Butlast()); // count:0 []
    }
}

実行結果です。

count:2 [1 2]
count:1 [1]
count:0 []
count:0 []

参考

Iterate メソッド

初期値と「次の値」を返す関数を引数で受け取り、無限シーケンスを返す Iterate メソッドを作ってみました。

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

class Iter {
    public static IEnumerable<T> Iterate<T>(T init, Func<T, T> next) {
        T x = init;
        while (true) {
            yield return x;
            x = next(x);
        }
    }
}

class Program {
    static void Display<T>(IEnumerable<T> xs) {
        Console.WriteLine(string.Join(" ", xs));
    }

    static void Main() {
        Display(Iter.Iterate(0, e => e + 8).Take(11));
        // => 0 8 16 24 32 40 48 56 64 72 80
        Display(Iter.Iterate(0, e => e + 8).TakeWhile(e => e <= 80));
        // => 0 8 16 24 32 40 48 56 64 72 80
    }
}

実行結果です。

0 8 16 24 32 40 48 56 64 72 80
0 8 16 24 32 40 48 56 64 72 80

参考

MinBy, MaxBy を作る

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

public static class Ext {
    public static T MinBy<T, U>(this IEnumerable<T> xs, Func<T, U> key) where U : IComparable<U> {
        return xs.Aggregate((a, b) => key(a).CompareTo(key(b)) < 0 ? a : b);
    }

    public static T MaxBy<T, U>(this IEnumerable<T> xs, Func<T, U> key) where U : IComparable<U> {
        return xs.Aggregate((a, b) => key(a).CompareTo(key(b)) > 0 ? a : b);
    }
}

class Program {
    static void Main() {
        var xs = new List<int[]> {
            new[] { 1, 2 },
            new[] { 20, 50 },
            new[] { 30, 22 },
            new[] { 50, -100 },
        };

        // 1 番目の要素が最小
        var a = xs.MinBy(e => e[1]);
        Console.WriteLine(string.Join(" ", a)); // 50 -100

        // 1 番目の要素が最大
        var b = xs.MaxBy(e => e[1]);
        Console.WriteLine(string.Join(" ", b)); // 20 50

        // 要素数が 1 の場合
        var c = new[] { 123 }.MinBy(e => e);
        Console.WriteLine(c); // 123

        // 要素数が 0 の場合
        // var d = new int[0].MinBy(e => e);
        // Console.WriteLine(d); // System.InvalidOperationException: Sequence contains no elements
    }
}

実行結果です。

50 -100
20 50
123

リンク

比較関数を渡せる GroupBy メソッドを作る

比較関数を渡せる GroupBy メソッドを作ってみました。

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

class Program {
    static IEnumerable<T[]> GroupBy<T>(IEnumerable<T> xs, Func<T, T, bool> match) {
        var ls = new List<T>();
        foreach (var x in xs) {
            if (ls.Count == 0 || match(ls[ls.Count-1], x)) {
                ls.Add(x);
            }
            else {
                yield return ls.ToArray();
                ls.Clear();
                ls.Add(x);
            }
        }
        if (ls.Count > 0) {
            yield return ls.ToArray();
        }
    }

    static void Display<T>(IEnumerable<T[]> xxs) {
        var xs = xxs.Select(e => string.Format("[{0}]", string.Join(", ", e)));
        var s = string.Format("[{0}]", string.Join(", ", xs));
        Console.WriteLine(s);
    }

    static void Main() {
        Display(GroupBy(new[] { 1, 2, 1, 2, 5, 1 }, (a, b) => a < b));
        // => [[1, 2], [1, 2, 5], 1]

        Display(GroupBy(new[] { 1, 1, 1, 2, 2, 5 }, (a, b) => a == b));
        // => [[1, 1, 1], [2, 2], [5]

        Display(GroupBy(new[] { 1, 1, 1, 2, 2, 5 }, (a, b) => a != b));
        // => [[1], [1], [1, 2], [2, 5]]

        Display(GroupBy("Mississippi", (a, b) => a == b));
        // => [[M], [i], [s, s], [i], [s, s], [i], [p, p], [i]]
    }
}

実行結果です。

[[1, 2], [1, 2, 5], [1]]
[[1, 1, 1], [2, 2], [5]]
[[1], [1], [1, 2], [2, 5]]
[[M], [i], [s, s], [i], [s, s], [i], [p, p], [i]]

リンク