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]]

リンク

1 次元配列の回転

1 次元配列を回転するサンプルです。

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

class Program {
    static T[] Rotate<T>(T[] xs, int n) {
        var ret = new T[xs.Length];
        if (ret.Length == 0) return ret;

        int m = n % ret.Length;
        for (int i = 0; i < ret.Length; i++) {
            ret[i] = xs[m++];
            if (m == ret.Length) m = 0;
        }
        return ret;
    }

    static void Main() {
        var xs = Enumerable.Range(1, 5).ToArray();
        for (int i = 0; i < 7; i++) {
            var ys = Rotate(xs, i);
            Console.WriteLine(string.Join(" ", ys));
        }
    }
}

実行結果です。

1 2 3 4 5
2 3 4 5 1
3 4 5 1 2
4 5 1 2 3
5 1 2 3 4
1 2 3 4 5
2 3 4 5 1

インスタンスが null の場合でも拡張メソッドの呼び出しは可能

インスタンスが null の場合にメソッド呼び出しを行うと、System.NullReferenceException 例外が生成されますが、拡張メソッドの場合は、インスタンスが null でも呼び出しが可能です。

using System;

public class Foo {
    public void Hello() {
        Console.WriteLine("Hello!!");
    }
}

public static class Extension {
    public static void Goodbye(this Foo foo) {
        Console.WriteLine("Goodbye!!");
    }
}

class Program {
    static void Main() {
        Foo foo = new Foo();
        foo.Hello();       // Hello
        foo.Goodbye();     // Goodbye!!

        Foo foo2 = null;
        // foo2.Hello();   // System.NullReferenceException
        foo2.Goodbye();    // Goodbye!!
    }
}

実行結果です。

Hello!!
Goodbye!!
Goodbye!!

Partition 関数

シーケンスの要素を、述語関数を満たす要素と満たさない要素に分割する Partition 関数(メソッド)を作ってみました。

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

class Program {
    static Tuple<List<T>, List<T>> Partition<T>(IEnumerable<T> xs, Predicate<T> pred) {
        var ok = new List<T>();
        var ng = new List<T>();
        foreach (var x in xs) {
            if (pred(x)) {
                ok.Add(x);
            }
            else {
                ng.Add(x);
            }
        }
        return Tuple.Create(ok, ng);
    }

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

        // 偶数と奇数に分割する
        var t = Partition(xs, e => e % 2 == 0);
        Console.WriteLine(string.Join(", ", t.Item1)); // 2, 4
        Console.WriteLine(string.Join(", ", t.Item2)); // 1, 3, 5

        // ゼロまたは正と負に分割する
        var t2 = Partition(new[] { 1, -3, 22, -90, -100 }, e => e >= 0);
        Console.WriteLine(string.Join(", ", t2.Item1)); // 1, 22
        Console.WriteLine(string.Join(", ", t2.Item2)); // -3, -90, -100
    }
}

実行結果です。

2, 4
1, 3, 5
1, 22
-3, -90, -100

リンク

Predicate(T) デリゲート (System)

Group 関数

シーケンス内の隣り合う要素のうち、同じ値のものを配列にまとめる関数 Group を作ってみました。

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

static class Iter {
    public static IEnumerable<T[]> Group<T>(IEnumerable<T> xs) {
        var ls = new List<T>();
        foreach (var x in xs) {
            if (ls.Count == 0 || ls[0].Equals(x)) {
                ls.Add(x);
            }
            else {
                yield return ls.ToArray();
                ls.Clear();
                ls.Add(x);
            }
        }
        if (ls.Count > 0) {
            yield return ls.ToArray();
        }
    }
}

class Program {
    static void Display<T>(IEnumerable<T[]> xxs) {
        var ls = new List<string>();
        foreach (var xs in xxs) {
            ls.Add(string.Format("[{0}]", string.Join(", ", xs)));
        }
        var s = string.Format("[{0}]", string.Join(", ", ls));
        Console.WriteLine(s);
    }

    static void Main() {
        Display(Iter.Group(new[] { 1 }));
        Display(Iter.Group(new[] { 1, 1 }));
        Display(Iter.Group(new int[0]));
        Display(Iter.Group(new[] { 1, 1, 0, 0, 1, 1, 1, 2, 2, 0, 0, 0 }));
        Display(Iter.Group(new[] { 1, 2, 3, 0, 0 }));
    }
}

実行結果です。

[[1]]
[[1, 1]]
[]
[[1, 1], [0, 0], [1, 1, 1], [2, 2], [0, 0, 0]]
[[1], [2], [3], [0, 0]]