UniRx: Start

Observable.Start メソッドは、引数で指定した Func<T> 関数から生成したデータを通知します。

using System;
using UniRx;
using UnityEngine;

public class Main : MonoBehaviour
{
    private IObservable<DateTime> _observable;

    void Start()
    {
        _observable = Observable.Start(() => DateTime.Now);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // スペースキーを押すたびに Subscribe する
            _observable.Subscribe(e => Debug.Log(e),
                                  () => Debug.Log("OnCompleted"));
        }
    }
}

実行結果です。スペースキーを押すたびに出力される時刻が更新されていますね。

2018/08/20 21:18:20
OnCompleted
2018/08/20 21:18:22
OnCompleted
2018/08/20 21:18:24
OnCompleted

参考

UniRx: Return

    void Start()
    {
        var s = Observable.Return(100);
        s.Subscribe(e => Debug.Log(e), () => Debug.Log("OnCompleted"));
    }

実行結果です。

100
OnCompleted

UniRx の Return の実装を読む。パフォーマンスを上げるために、通知する値の型に応じて実装をもっているようだ。 int の小さな値は内部でキャッシュしている。

参考

ToLookup 拡張メソッド

以前に Partition 関数 - C#練習日記 を作りましたが、 Linq には ToLookup 拡張メソッド があり、このメソッドを使うことで同様のグルーピングを行うことができます。

using System;
using System.Linq;

class Program
{    
    static void Main()
    {
        var xs = new[] { 1, 2, 3, 4, 5 };

        // 偶数と奇数でグループ分けする
        var d = xs.ToLookup(e => e % 2 == 0);
        Console.WriteLine(string.Join(", ", d[true]));  // 2, 4
        Console.WriteLine(string.Join(", ", d[false])); // 1, 3, 5
        
        // 正の整数とそれ以外でグループ分けする
        var d2 = xs.ToLookup(e => e > 0);
        Console.WriteLine(d2[true].Count());  // 5 
        Console.WriteLine(d2[false].Count()); // 0
    }
}

ToLookup の戻り値は、ILookup<TKey, TElement> です。 各キーに対する要素は IEnumerable<TElement> で取得できます。

プロパティへの代入がコンストラクタ内のみなら、そのプロパティに 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"); 
    }
}