Replace, Intersperse, Init
- 2009-11-01
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が標準搭載されていますし。