プロパティへの代入がコンストラクタ内のみなら、そのプロパティに private set を定義する必要はない

using System;

class Person
{
    public string Name { get; } // 代入はコンストラクタでのみ。private set; は不要
    public int Age { get; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

class Program
{    
    public static void Main()
    {
        var p = new Person("Bob", 23);
        Console.WriteLine($"{p.Name} {p.Age}"); // Bob 23

        // コンパイルエラー: The Property 'Person.Name' has no setter
        // p.Name = "Alice";
    }
}

N 番目の要素を O(1) で取得する Cycle クラスを作る

数列 a = { 0, 1, 0, -1, 0, 1, 0, -1, 0, 1, 0, -1, ... } のような 4 周期で繰り返す数列で、かつ N 番目の要素を O(1) で取得する Cycle クラスを作ってみました。

// 0, 1, ... を繰り返す数列
var zeroOne = new Cycle<int>(new[] { 0, 1 });
Console.WriteLine(string.Join(", ", zeroOne.Take(5)));
//=> 0, 1, 0, 1, 0
Console.WriteLine(string.Join(", ", new[] { 0, 1, 2, 3, 5 }.Select(e => zeroOne[e])));
//=> 0, 1, 0, 1, 1

// 0, 1, 0, -1, ... を繰り返す数列
var seq = new Cycle<int>(new[] { 0, 1, 0, -1 });
Console.WriteLine(string.Join(", ", seq.Take(8)));
//=> 0, 1, 0, -1, 0, 1, 0, -1
Console.WriteLine(string.Join(", ", new[] { 0, 1, 2, 3, 5, 7 }.Select(e => seq[e])));
//=> 0, 1, 0, -1, 1, -1

Cycle クラス:

class Cycle<T> : IEnumerable<T>
{
    private readonly T[] _array;
    
    public Cycle(T[] array)
    {
        if (array == null) throw new ArgumentNullException(nameof(array));
        if (array.Length == 0) throw new ArgumentException(nameof(array));

        _array = array;
    }
    
    public IEnumerator<T> GetEnumerator()
    {
        while (true)
        {
            foreach (var x in _array)
                yield return x;
        }        
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public T this[int index] => _array[index % _array.Length];
}

エラトステネスの篩

haskell.g.hatena.ne.jp

上の記事をみて、エラトステネスの篩を C# で書いてみました。

// 2, 3, 4, 5, ...
static IEnumerable<int> Nums()
{
    int n = 2;
    while (true)
        yield return n++;
}

static Func<int, bool> MakePrimeFilter(int prime, Func<int, bool> isPrime)
{
    // x はこれまで登場した素数で割りきれない、かつ prime でも割り切れないならば、x は素数である
    return x => isPrime(x) && x % prime != 0;
}

static IEnumerable<int> Primes()
{
    Func<int, bool> isPrime = n => true;
    foreach (var x in Nums())
    {
        if (isPrime(x))
        {
            yield return x;
            isPrime = MakePrimeFilter(x, isPrime);
        }
    }
}

お試しコード。

public static void Main(string[] args)
{
    Console.WriteLine(string.Join(", ", Primes().Take(10)));    
    // => 2, 3, 5, 7, 11, 13, 17, 19, 23, 29

    Console.WriteLine(Primes().ElementAt(1000)); // 7927
    
    // 10,000 より大きな最小の素数は?
    Console.WriteLine(Primes().SkipWhile(e => e <= 10000).First()); // 10007
}

※ このコードは効率はよくありません。関数を重ねるコードの書き方の練習用です。

双子素数も求めてみます。

// 双子素数を求める
static IEnumerable<Tuple<int, int>> Twins()
{
    foreach (var pair in Primes().Zip(Primes().Skip(1), (a, b) => Tuple.Create(a, b)))
    {
        if (pair.Item1 + 2 == pair.Item2)
            yield return pair;
    }
}

public static void Main(string[] args)
{
    var ts = Twins().Take(10);
    Console.WriteLine(string.Join(", ", ts.Select(e => $"({e.Item1}, {e.Item2})")));
    // => (3, 5), (5, 7), (11, 13), (17, 19), (29, 31), (41, 43), (59, 61), (71, 73), (101, 103), (107, 109)
}

元コード:

twins = filter ((2==) . uncurry subtract) $ s zip tail primes

上の ((2==) . uncurry subtract) のところがよく分からなかったので分解してみます。

subtract は、subtract a b で b - a を計算します。

Prelude> :t subtract
subtract :: Num a => a -> a -> a
Prelude> subtract 1 5
4

uncurry は、uncurry (+) (a, b)(+) a b を計算します。

Prelude> :t uncurry
uncurry :: (a -> b -> c) -> (a, b) -> c
Prelude> uncurry (+) (1, 3)
4

(2==) は、2 と等しいかを判定する関数です。

Prelude> :t (2==)
(2==) :: (Num a, Eq a) => a -> Bool
Prelude> (2==) 2
True
Prelude> (2==) 3
False

よって、(2==) . uncurry subtract は、タプル (a, b) を受け取って substract a b すなわち b - a が 2 と等しいかを判定する関数、ということになります。

Prelude> a = (2==) . uncurry subtract
Prelude> :t a
a :: (Eq a, Num a) => (a, a) -> Bool
Prelude> a (1, 3)
True
Prelude> a (1, 4)
False

IEnumerable<T> のランダムな要素を選択する

d.hatena.ne.jp

上の記事で紹介されている「要素全体をメモリに保持せずに、ランダムな要素を選択する」アルゴリズムが面白いです。

C# で書いてみました。

public static class Extensions
{
    // source が空だったり、配列やリストの場合の対応は後述
    public static T RandomChoice<T>(this IEnumerable<T> source, Random random)
    {              
        T selected = default(T);
        int n = 1;
        foreach (var e in source)
        {
            if (random.Next(n) == 0)
                selected = e;
            n++;
        }
        return selected;
    }
}

お試しコード。

class Program
{   
    static void Main()
    {
        var xs = Enumerable.Range(0, 10).ToArray();
        var random = new Random();
        var freq = new int[xs.Length];
        for (int i = 0; i < 1000000; i++)
        {
            int x = xs.RandomChoice(random);
            freq[x]++;
        }
        
        for (int i = 0; i < freq.Length; i++)
            Console.WriteLine("{0} : {1}", i, freq[i]);
    }
}

実行結果です。

0 : 100246
1 : 99682
2 : 100206
3 : 100504
4 : 99295
5 : 100318
6 : 99000
7 : 100538
8 : 100414
9 : 99797

配列やリストへの対応

IEnumerable<T> の元が配列やリストの場合は、そもそも要素をループする必要がありません。Enumerable.Last が行っているように、IList<T> を実装しているなら、ループを回さないようにします。

要素が空かどうかのチェックも入れておきます。

public static class Extensions
{
    public static T RandomChoice<T>(this IEnumerable<T> source, Random random)
    {   
        if (source == null) throw new ArgumentNullException(nameof(source));

        var list = source as IList<T>;
        if (list != null)
        {
            if (list.Count > 0) return list[random.Next(list.Count)];
        }
        else
        {
            using (var e = source.GetEnumerator())
            {
                if (e.MoveNext())
                {
                    int n = 1;
                    T selected = default(T);
                    do
                    {
                        if (random.Next(n) == 0)
                            selected = e.Current;
                        n++;
                    } while (e.MoveNext());

                    return selected;
                }
            }
        }
        throw new InvalidOperationException("source is empty"); 
    }
}

インターフェース型の変数への代入と 3 項演算子を組み合わせるとコンパイルエラーになる

クラス A と、クラス B が IDisposable を実装しているとします。 そのときに、3 項演算子を用いて、以下のように書くことはできないようです。

class A : IDisposable { ... }
class B : IDisposable { ... }

// コンパイルエラーになる: There is no implicit conversion between 'A' and 'B'
IDisposable c = true ? new A() : new B();

IDisposable でキャストすれば問題なくコンパイルできます。

// OK
IDisposable c = true ? (IDisposable)new A() : (IDisposable)new B();

あるいは、いったん IDisposable 型の変数に格納しておいてから、3 項演算子を使う方法もあります。

IDisposable a = new A();
IDisposable b = new B();
var c = true ? a : b; // OK

背景

あるオブジェクトの配列があり、それをいくつかの異なる方法でソートする必要がでてきました。 そのため、それぞれの比較方法に応じた IComparer を実装するクラスを用意して、 それを Sort メソッドに渡してソート方法を切り替えようと考えました。

Sortメソッドに IComparer を渡すときに 3 項演算子を使おうとしたのですが、 コンパイルエラーになりましたので、かわりに IComparer を返すメソッドを作成するようにしました。

おおよそ以下のようなコードを書きました。

using System;
using System.Collections.Generic;

class Foo {}

// Foo をある方法 A で比較
class ComparerA : IComparer<Foo>
{
    public int Compare(Foo x, Foo y)
    {
        throw new NotImplementedException();
    }
}

// Foo をある方法 B で比較
class ComparerB : IComparer<Foo> 
{
    public int Compare(Foo x, Foo y)
    {
        throw new NotImplementedException();
    }
}

class Program
{
    static void Main()
    {
        var foos = new[] { new Foo() };
        
        Array.Sort(foos, new ComparerA()); // 方法 A でソート
        Array.Sort(foos, new ComparerB()); // 方法 B でソート
        
        // フラグでソート方法を切り替える。
        // 本当はこう書きたかったがコンパイルエラー:
        // There is no implicit conversion between 'ComparerA' and 'ComparerB'        
        // Array.Sort(foos, true ? new ComparerA() : new ComparerB());
        
        // 3 項演算子が使えないので、IComarer を返すメソッドを用意した
        Array.Sort(foos, CreateComparer(true));
    }

    static IComparer<Foo> CreateComparer(bool flag)
    {
        if (flag) return new ComparerA();
        return new ComparerB();
    }
}

構造体を定義すると Equals が自動的に実装されるが、IEquatable<T> を実装した方がよい

方法: 型の値の等価性を定義する (C# プログラミング ガイド) | Microsoft Docs より:

構造体を定義すると、System.Object.Equals(Object) メソッドの System.ValueType オーバーライドから継承された値の等価性が既定で実装されます。 この実装では、リフレクションを使用して、型のフィールドとプロパティをすべて調べます。 この実装によって正しい結果が生成されますが、その型専用に記述したカスタム実装と比較すると、処理にかなり時間がかかります。

自作の Point 構造体を定義して、配列、HashSet, Dictionary から値を検索できるか確認してみます。値の等価性の確認です。

using System;
using System.Collections.Generic;

class Program {
    struct Point {
        public int X { get; }
        public int Y { get; }

        public Point(int x, int y) {
            X = x;
            Y = y;
        }
    }

    static void Main() {
        void p<T>(T o) => Console.WriteLine(o);

        var a = new Point(1, 2);
        var b = new Point(1, 2);

        // a を格納した配列から b を見つける
        p(Array.IndexOf(new[] { a }, b)); // 0

        // a を格納した HashSet から b を見つける
        var hs = new HashSet<Point>() { a };
        p(hs.Contains(b)); // True

        // a を格納した Dictionary から b を見つける
        var d = new Dictionary<Point, bool>() { [a] = true };
        p(d.ContainsKey(b)); // True
    }
}

実行結果です。

0
True
True

構造体のメンバーごとの比較が行われており、配列、HashSet、Dictionary に格納した場合でも値を見つけることができていることが確認できました。

次に、IEquatable<T> を実装した場合と、速度比較をしてみます。

using System;
using System.Collections.Generic;

class Program {
    struct Point {
        public int X { get; }
        public int Y { get; }

        public Point(int x, int y) {
            X = x;
            Y = y;
        }
    }

    // IEquatable<T> を実装した Point 構造体
    struct PointImplementsIEquatable : IEquatable<PointImplementsIEquatable> {
        public int X { get; }
        public int Y { get; }

        public PointImplementsIEquatable(int x, int y) {
            X = x;
            Y = y;
        }

        public bool Equals(PointImplementsIEquatable other) {
            return X == other.X && Y == other.Y;
        }

        public override bool Equals(object other) {
            if (other is PointImplementsIEquatable)
                return Equals((PointImplementsIEquatable)other);
            return false;
        }

        public override int GetHashCode() {
            return X ^ Y;
        }
    }

    static void Benchmark(int n) {
        var sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        var xs = new[] { new Point(1, 2), };
        for (int i = 0; i < n; i++) {
            Array.IndexOf(xs, xs[0]);
        }
        sw.Stop();
        Console.WriteLine($"Point             = {sw.ElapsedMilliseconds}ms");

        sw.Restart();
        var ys = new[] { new PointImplementsIEquatable(1, 2), };
        for (int i = 0; i < n; i++) {
            Array.IndexOf(ys, ys[0]);
        }
        sw.Stop();
        Console.WriteLine($"Point(IEquatable) = {sw.ElapsedMilliseconds}ms");
    }

    static void Main() {
        Benchmark(10_000_000);
    }
}

実行結果です。

Point             = 895ms
Point(IEquatable) = 281ms

IEquatable<T>を実装したほうが速いようです。Equals() 呼び出し時のボックス化も重いんですかね。 速度のことを考えると、常に IEquatable<T> を実装したほうが良さそうです。

参考

Mono 5.0 と C# 7 の Local Functions, Tuples で遊ぶ

Mono 5.0 と C# 7 で遊びます。

まずは、mono のアップグレード。

$ brew upgrade mono

mono のバージョンの確認。

$ mono --version
Mono JIT compiler version 5.0.1.1 (2017-02/5077205 Wed May 31 14:47:54 BST 2017)
Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com

csc コマンドでコンパイルして実行します。

$ cat a.cs
using System;

class Program {
    static void Main() {
        Console.WriteLine("Hello C# 7");
    }
}

$ csc a.cs
Microsoft (R) Visual C# Compiler version 2.0.0.61404
Copyright (C) Microsoft Corporation. All rights reserved.

$ mono a.exe
Hello C# 7

C# 7 で遊ぶ準備が整いました。

Local Functions

メソッド名と同じ名前のローカル関数を定義できるようです。 以下では、メソッド名と同じ Add, Sub という名前のローカル関数を定義しています。

using System;

class Program {
    static int Add(int a, int b) {
        int Add(int x, int y) {
            return x + y;
        }
        return Add(a, b);
    }

    static int Sub(int a, int b) {
        int Sub(int x, int y) => x - y;
        return Sub(a, b);
    }

    static void Main() {
        Console.WriteLine(Add(10, 23));  // 33
        Console.WriteLine(Sub(100, 88)); // 12
    }
}

ローカル関数の引数には、外側のメソッドと同じ引き数名は付けられないようです。

using System;

class Program {
    static int Add1(int n) {
        // コンパイルエラー: 引数名 n は外側のメソッドで使われている
        int Add1(int n) => n + 1;
        return Add1(n);
    }

    static void Main() {
    }
}

コンパイルすると、以下のコンパイルエラーが表示されます。

$ csc a.cs
a.cs(6,22): error CS0136: A local or parameter named 'n' cannot be declared in this scope 
because that name is used in an enclosing local scope to define a local or parameter

Tuples

using System;

class Program {
    static void Main() {
        var t = ("Bob", 10);
        Console.WriteLine($"{t.Item1} {t.Item2}"); // Bob 10

        var t2 = (Name: "Bob", Age: 10);
        Console.WriteLine($"{t2.Name} {t2.Age}"); // Bob 10

        (string name, int age) = ("Bob", 10);
        Console.WriteLine($"{name} {age}"); // Bob 10

        (string Name, int Age) bob = ("Bob", 10);
        Console.WriteLine($"{bob.Name} {bob.Age}"); // Bob 10

        var (name2, age2) = ("Bob", 10);
        Console.WriteLine($"{name2} {age2}"); // Bob 10
    }
}

メソッドの戻り値にも使えますので、複数の値を返すときにわざわざ新しい型を作る必要がなくなります。

using System;

class Program {
    // a を b で割ったときの商と余りを返す
    static (int Quotient, int Remainder) DivMod(int a, int b) {
        return (a / b, a % b);
    }

    static void Main() {
        (int q, int r) = DivMod(10, 3);
        Console.WriteLine($"{q} {r}"); // 3 1

        var x = DivMod(11, 3);
        Console.WriteLine($"{x.Quotient} {x.Remainder}"); // 3 2

        var (q2, r2) = DivMod(12, 3);
        Console.WriteLine($"{q2} {r2}"); // 4 0
    }
}

参考