Replace, Intersperse, Init

static void Main(string[] args)
{
    // 1,2,3,4,5,6,7,8,9,10
    var source = Enumerable.Range(1, 10);
    // 偶数を-1に置換する
    var replace = source.Replace(i => i % 2 == 0, -1);
    // 値を挟み込む(1,100,2,100,...9,100,10)
    var intersperse = source.Intersperse(100);
    // 末尾一個を省く(1..9)
    var init1 = source.Init();
    // 末尾三個を省く(1..7)
    var init2 = source.Init(3);
}

public static IEnumerable<T> Replace<T>(this IEnumerable<T> source, Func<T, bool> predicate, T replacement)
{
    foreach (var item in source)
    {
        if (predicate(item)) yield return replacement;
        else yield return item;
    }
}

public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> source, T value)
{
    var isFirst = true;
    foreach (var item in source)
    {
        if (!isFirst) yield return value;
        yield return item;
        isFirst = false;
    }
}

public static IEnumerable<T> Init<T>(this IEnumerable<T> source)
{
    return source.Init(1);
}

public static IEnumerable<T> Init<T>(this IEnumerable<T> source, int count)
{
    if (count == 0)
    {
        foreach (var item in source) yield return item;
        yield break;
    }

    var q = new Queue<T>(count);
    foreach (var item in source)
    {
        if (q.Count == count) yield return q.Dequeue();
        q.Enqueue(item);
    }
}

という拡張メソッド。もういい加減いつになるのか分からなくなってしまって悲しいlinq.jsの次のリリースにはこれらを入れます。IntersperseとInitの元ネタはMono.Rocksから。それの更に元はHaskellのようですねん。

Initは何度となく欲しい!と思ったシーンがあるので、きっと便利。長さが不定で前から後ろに走るLinqでは、後ろから幾つ、というのは標準では出来ないんですね。ReverseしてSkipしてReverseするか、一度ToArrayしてから切り出したりしか手がなくて。最後の一個だけ省きたいとか、よくあります。しかしInitって関数名は意味不明で少々アレかも。Mono.RocksではExceptLastに改称されていました。そうですねえ、CarとかCdrを引き摺る必要はないように、ExceptLastのほうが良さそうですね。

実のところInitがあればIntersperseも標準Linq演算子で定義出来ます。

var intersperse = source
    .SelectMany(i => Enumerable.Repeat(i, 1).Concat(Enumerable.Repeat(100, 1)))
    .Init();

Initの便利さが分かる。そう、こういうのやると、どうしても末尾に一個ゴミが付いてきちゃて、それをスマートに除去するのは出来ないのですよね。あって良かったInit。そしてSelectManyの万能さは異常。Repeat(value, 1)とかRepeat(value, int.MaxValue)も超多用。記述があまりにも冗長になって泣けますが。

RxFrameworkにはObservable.Return(value)という、Repeat(value, 1)と同様のものが定義されていたりします。それとObservable.Cons(value,IObservable)というConcatの単体バージョンみたいなものもあります(lispのconsと同じイメージです)。だから、上のをRxでやるならば

var intersperse = Observable.Range(1, 10)
    .SelectMany(i => Observable.Cons(i, Observable.Return(100)))
    .ToEnumerable();
    .Init();

となります。あまりスッキリしてない? まあ、そうかも。でも、決め打ちの1って書くの嫌なものなので、それが省けるってのは嬉しいものです。定数を使うなら(int)decimal.Oneという手もありますが、まあ、馬鹿らしい。私はstring.Emptyよりも""を使う派なので、それはちょっとありえない。ちなみに、""を選ぶ理由は、タイプ数が少ないという他に、文字列であることが色分けされて表示されるため、string.Emptyよりも遥かに視認性が良いからです。こういうのはIDEを含めて考えないとね。パフォーマンス云々の話は些細なことなので個人的にはどうでもいい。

Rxの記事は、細かいネタは溜まっているので、近いうちにまた書きたいと思います。VS2010 Beta2ではIObservableが標準搭載されていますし。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive