SEND + MORE = MONEY

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

class Program {
    static int ToInt(string s, int[] map) {
        int x = 0;
        foreach (var c in s) {
            x = 10 * x + map[c];
            if (x == 0 && s.Length > 1) return -1; // leading zero
        }
        return x;
    }

    static void Solve_1(int p, char[] cs, int used, int[] map, string[] ss) {
        if (p == cs.Length) {
            var a = ss.Take(ss.Length - 1).Select(e => ToInt(e, map));
            if (a.Contains(-1)) return; // 不正な変換結果

            int b = ToInt(ss.Last(), map);
            if (a.Sum() == b) {
                Console.WriteLine("{0} = {1}", string.Join(" + ", ss.Take(ss.Length - 1)), ss.Last());
                Console.WriteLine("{0} = {1}", string.Join(" + ", a), b);
            }
            return;
        }

        for (int i = 0; i < 10; i++) {
            if ((used & (1 << i)) != 0) continue;

            map[cs[p]] = i; // cs[p] に i を割り当てる
            Solve_1(p + 1, cs, used | (1 << i), map, ss);
        }
    }

    static void Solve(params string[] ss) {
        var cs = new HashSet<char>(ss.SelectMany(e => e)).ToArray();
        if (cs.Length > 10) throw new Exception(); // 10 種類より多いと数字を割り当てられない

        var map = new int[128]; // どの文字にどの数字を割り当てたか
        Solve_1(0, cs, 0, map, ss);
        Console.WriteLine();
    }

    static void Main() {
        Solve("SEND", "MORE", "MONEY");
        Solve("CROSS", "ROADS", "DANGER");
        Solve("SIX", "SEVEN", "SEVEN", "TWENTY");
    }
}

実行結果です。

$ time mono a.exe
SEND + MORE = MONEY
9567 + 1085 = 10652

CROSS + ROADS = DANGER
96233 + 62513 = 158746

SIX + SEVEN + SEVEN = TWENTY
650 + 68782 + 68782 = 138214

mono a.exe  5.38s user 0.03s system 99% cpu 5.419 total

リンク

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

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

リンク