LINQ to Objects & Interactive Extensions & linq.js 全メソッド概説
- 2011-08-10
@ITに以前書いたLINQの基礎知識の話が載りました -> LINQの仕組み&遅延評価の正しい基礎知識 - @IT。ああ、もっとしっかり書いていれば(図もへっぽこだし)、と思ったり思わなかったり。それでも校正していただいたのと、細部は修正してあるので、元のものよりも随分と読みやすいはずです。そういえばで1月頭の話なんですね、姉妹編としてRxの基礎知識もやるつもりだったのにまだやってないよ!
ところでそもそも基礎知識といったら標準クエリ演算子が何をできるかではないのでしょうか?知ってるようで知らない標準クエリ演算子。101 LINQ SamplesもあるしMSDNのリファレンスは十分に充実していますが、しかし意外と見逃しもあるかもしれません。また、Interactive Extensionsで何が拡張されているのかは知っていますか?ついでにJS実装のlinq.jsには何があるのか知っていますか?
そんなわけで、LINQ to Objects、Ix、linq.jsの全メソッドを一行解説したいと思います。
LINQ to Objects
いわゆる、標準クエリ演算子。.NET 3.5から使えます。.NET4.0からはZipメソッドが追加されました。なお、サンプルと実行例はlinq.js Referenceに「完全に」同じ挙動をするJS実装での例がありますので、そちらを参照にどうぞ。こういう場合はJS実装だと便利ですね。
| Aggregate | 汎用的な値算出 |
| All | 条件に全て一致するか |
| Any | 条件に一つでも一致するか、引数なしの場合は空かどうか |
| AsEnumerable | IEnumerable<T>へアップキャスト |
| Average | 平均 |
| Cast | 値のダウンキャスト、主な用途はIEnumerableからIEnumerable<T>への変換 |
| Concat | 引数のシーケンスを後ろに連結 |
| Contains | 値が含まれているか、いわばAnyの簡易版 |
| Count | シーケンスの件数 |
| DefaultIfEmpty | シーケンスが空の場合、デフォルト値を返す(つまり長さ1) |
| Distinct | 重複除去 |
| ElementAt | 指定インデックスの要素の取得 |
| ElementAtOrDefault | 指定インデックスの要素の取得、なければデフォルト値を返す |
| Empty | 空シーケンスの生成 |
| Except | 差集合・差分だけ、集合なので重複は除去される |
| First | 最初の値の取得、ない場合は例外が発生 |
| FirstOrDefault | 最初の値を取得、ない場合はデフォルト値を返す |
| GroupBy | グループ化、ToLookupの遅延評価版(ただしストリーミングでの遅延評価ではない) |
| GroupJoin | 右辺をグループにして結合、外部結合をしたい時にDefaultIfEmptyと合わせて使ったりもする |
| Intersect | 積集合・共通の値だけ、集合なので重複は除去される |
| Join | 内部結合 |
| Last | 最後の値を取得、ない場合は例外が発生 |
| LastOrDefault | 最後の値を取得、ない場合はデフォルト値を返す |
| LongCount | シーケンスの件数、longなので長い日も安心 |
| Max | 最大値 |
| Min | 最小値 |
| OfType | 指定した型の値だけを返す、つまりWhereとisが組み合わさったようなもの |
| OrderBy | 昇順に並び替え |
| OrderByDescending | 降順に並び替え |
| Range | 指定個数のintシーケンスの生成 |
| Repeat | 一つの値を繰り返すシーケンスの生成 |
| Reverse | 逆から列挙 |
| Select | 射影、関数の第二引数はインデックス |
| SelectMany | シーケンスを一段階平らにする、モナドでいうbind |
| SequenceEqual | 二つのシーケンスを値で比較 |
| Single | 唯一の値を取得、複数ある場合は例外が発生 |
| SingleOrDefault | 唯一の値を取得、複数ある場合はデフォルト値を返す |
| Skip | 指定個数だけ飛ばす |
| SkipWhile | 条件が正のあいだ飛ばす |
| Sum | 合計 |
| Take | 指定個数列挙、シーケンスの個数より多く指定した場合はシーケンスの個数分だけ |
| TakeWhile | 条件が正のあいだ列挙 |
| ThenBy | 同順の場合のソートキーの指定、昇順に並び替え |
| ThenByDescending | 同順の場合のソートキーの指定、降順に並び替え |
| ToArray | 配列に変換 |
| ToDictionary | 辞書に変換 |
| ToList | リストに変換 |
| ToLookup | 不変のマルチ辞書(一つのキーに複数の値を持つ)に変換 |
| Union | 和集合・両方の値全て、集合なので重複は除去される |
| Where | フィルタ |
| Zip | 二つのシーケンスの結合、長さが異なる場合短いほうに合わされる |
暗記する必要はなくて、なんとなくこういうのがあってこんな名前だったかなー、とぐらいに覚えておけば、IntelliSenseにお任せできるので、それで十分です。
リスト処理という観点からみるとLINQはかなり充実しているわけですが、更に他の言語と比較した場合の特色は、やはりクエリ構文。SelectManyへの構文は多くの言語が備えていますが(モナドの驚異を参照のこと、LINQはLINM:言語統合モナドである、というお話)、SQLの構文をベースにしたJoin、GroupBy、OrderByへの専用記法は、意外と、というか普通に便利。
特にJoinはあってよかったな、と思います、インメモリで色々なところからデータ引っ張ってきて結合などすると特に。一つぐらいの結合なら別にメソッド構文でいいのですが、フツーのSQLと同じように大量のjoinを並べる場合に、クエリ構文じゃないとシンドい。インメモリからデータベースまで統一的な記法で扱える、ということの凄さを実感するところ。
といっても、普段はほとんどメソッド構文で書いてるんですけどねー。あくまで、込み入った状況になるときだけクエリ構文にしています。クエリ構文では表現できないものが結構多いわけで、わざわざ、これはクエリ構文だけで表現できるからクエリ構文にするかー、とか考えるのもカッタルイので。あと、単純にIntelliSenseでポコポコ打ってるほうが快適、というのもあります。
クエリ構文は、モナドへの記法というよりも、強力なリスト内包表記といった印象も、HaskellへのOrder By, Group Byのペーパー見て思ったりなんかしたりして。
Ix
Ix(Interactive Extensions)はReactive Extensionsで、現在は実験的なものとして提供されている、Enumerableの拡張メソッド群。NuGetのIx_Experimental-Mainで入れるのが使いやすい感じ。InfoQ: LINQ to Objectsのためのインタラクティブエクステンションに解説が少し出ていましたが、少し不足していたり、間違っていたり(DoWhileとTakeWhileは一見似ていますが、挙動は全然異なるし、Forは別に全く興味深くなくSelectManyと同じです)したので、こちらの方が正しいです(キリッ
| Buffer | 指定個数分に区切って配列で値を列挙 |
| Case | 引数のIDictionaryを元に列挙するシーケンスを決める、辞書に存在しない場合はEmpty |
| Catch | 例外発生時に代わりに後続のシーケンスを返す |
| Concat | 可変長引数を受け入れて連結する生成子、拡張メソッド版はシーケンスのシーケンスを平らにする |
| Create | getEnumeratorを渡し任意のIEnumerableを生成する、といってもEnumerator.Createがないため、あまり意味がない |
| Defer | シーケンスの生成をGetEumerator時まで遅延 |
| Distinct | 比較キーを受け入れるオーバーロード |
| DistinctUntilChanged | 同じ値が続くものを除去 |
| Do | 副作用として各値にActionを適用し、値をそのまま列挙 |
| DoWhile | 一度列挙後に条件判定し、合致すれば再列挙 |
| Expand | 幅優先探索でシーケンスを再帰的に平らにする |
| Finally | 列挙完了時に指定したActionを実行 |
| For | SelectManyと一緒なので存在意義はない(Rxと鏡にするためだけに存在) |
| ForEach | foreach、関数の第二引数はインデックス |
| Generate | forループを模した初期値、終了判定、増加関数、値成形関数を指定する生成子 |
| Hide | IEnumerable<T>に変換、具象型を隠す |
| If | 条件が正なら指定したシーケンスを、負なら指定したシーケンス、もしくはEmptyで列挙する |
| IgnoreElements | 後に続くメソッドに何の値も流さない |
| IsEmpty | シーケンスが空か、!Any()と等しい |
| Max | IComparer<T>を受け入れるオーバーロード |
| MaxBy | 指定されたキーで比較し最大値だった値を返す |
| Memoize | メモ化、複数回列挙する際にキャッシュされた値を返す |
| Min | IComparer<T>を受け入れるオーバーロード |
| MinBy | 指定されたキーで比較し最小値だった値を返す |
| OnErrorResumeNext | 例外が発生してもしなくても後続のシーケンスを返す |
| Publish | ShareとMemoizeが合わさったような何か |
| Repeat | 無限リピート生成子、拡張メソッドのほうは列挙後に無限/指定回数最列挙 |
| Retry | 例外発生時に再度列挙する |
| Return | 単一シーケンス生成子 |
| Scan | Aggregateの算出途中の値も列挙する版 |
| SelectMany | 引数を使わず別のシーケンスに差し替えるオーバーロード |
| Share | 列挙子を共有 |
| SkipLast | 後ろからn個の値をスキップ |
| StartWith | 先頭に値を連結 |
| TakeLast | 後ろからn個の値だけを列挙 |
| Throw | 例外が発生するシーケンス生成子 |
| Using | 列挙完了後にDisposeするためのシーケンス生成子 |
| While | 列挙前に条件判定し合致したら列挙し、終了後再度条件判定を繰り返す生成子 |
みんな実装したことあるForEachが載っているのが一番大きいのではないでしょうか。別に自分で実装するのは簡単ですが、公式に(といってもExperimental Releaseですが)あると、全然違いますから。なお、何故ForEachが標準クエリ演算子にないのか、というのは、“foreach” vs “ForEach” - Fabulous Adventures In Codingによれば副作用ダメ絶対とのことで。納得は……しない。
Ixに含まれるメソッドは標準クエリ演算子では「できない」もしくは「面倒くさい」。Ixを知ることは標準だけでは何ができないのかを知ること。何ができないのかを知っていれば、必要な局面でIxを使うなり自前実装するなりといった対応がすぐに取れます、無理に標準クエリ演算子をこねくり回すことなく。例えばBufferやExpandは非常に有益で、使いたいシチュエーションはいっぱいあるんですが、標準クエリ演算子ではできないことです。
While, DoWhileとTakeWhileの違いは条件判定する箇所。While,DoWhileは列挙完了前/後に判定し、判定がtrueならシーケンスを再び全て列挙する。TakeWhileは通る値で毎回判定する。
PublishとMemoizeの違いは難解です。Memoizeは直球そのままなメモ化なんですが、Publishが凄く説明しづらくて……。Enumerator取得まではShareと同じく列挙子の状態は共有されてるんですが、取得後はMemoizeのようにキャッシュした値を返すので値の順番は保証される、といった感じです。うまく説明できません。
存在意義が微妙なものも、それなりにありますね。例えばIfとCaseとForなどは、正直、使うことはないでしょう。Usingも、これを使うなら別メソッドに分けて、普通にusing + yield returnで書いてしまうほうが良いと私は考えています。
Ixを加えると、ほとんど全てをLINQで表現出来るようになりますが、やりすぎて解読困難に陥ったりしがちなのには少し注意を。複雑になるようならベタベタ書かずに、一定の塊にしたものを別メソッドに分ければいいし、分けた先では、メソッドを組み合わせるよりも、yield returnで書いたほうが素直に表現出来るかもしれません。
適切なバランス感覚を持って、よきLINQ生活を!
linq.js
LINQ to ObjectsのJavaScript実装であるlinq.jsにも、標準クエリ演算子の他に(作者の私の趣味で)大量のメソッドが仕込んであるので、せっかくなのでそれの解説も。標準クエリ演算子にあるものは省きます(挙動は同一なので)。また、C#でIEqualityComparer<T>を受け取るオーバーロードは、全てキーセレクター関数のオーバーロードに置き換えられています。
一行サンプルと実行はlinq.js Referenceのほうをどうぞ。
| Alternate | 値の間にセパレーターを織り込む、HaskellのIntersperseと同じ |
| BufferWithCount | IxのBufferと同じ、次のアップデートでBufferに改称予定 |
| CascadeBreadthFirst | 幅優先探索でシーケンスを再帰的に平らにする、IxのExpandと同じ |
| CascadeDepthFirst | 深さ優先探索でシーケンスを再帰的に平らにする |
| Catch | IxのCatchと同じ |
| Choice | 引数の配列、もしくは可変長引数をランダムに無限に列挙する生成子 |
| Cycle | 引数の配列、もしくは可変長引数を無限に繰り返す生成子 |
| Do | IxのDoと同じ |
| Finally | IxのFinallyと同じ |
| Flatten | ネストされた配列を平らにする |
| Force | シーケンスを列挙する |
| ForEach | IxのForEachと同じ |
| From | 配列やDOMなど長さを持つオブジェクトをEnumerableに変換、linq.jsの要の生成子 |
| Generate | ファクトリ関数を毎回実行して値を作る無限シーケンス生成子、IxのGenerateとは違う(IxのGenerateはUnfoldで代用可) |
| IndexOf | 指定した値を含む最初のインデックス値を返す |
| Insert | 指定したインデックスの箇所に値を挿入、Insert(0, value)とすればIxのStartWithと同じ |
| LastIndexOf | 指定した値を含む最後のインデックス値を返す |
| Let | 自分自身を引数に渡し、一時変数を使わず自分自身に変化を加えられる |
| Matches | 正規表現のマッチ結果をシーケンスとして列挙する生成子 |
| MaxBy | IxのMaxByと同じ |
| MemoizeAll | IxのMemoizeと同じ、次のアップデートでMemoizeに改称予定 |
| MinBy | IxのMinByと同じ |
| Pairwise | 隣り合う要素とのペアを列挙 |
| PartitionBy | キーで指定した同じ値が続いているものをグループ化する |
| RangeDown | 指定個数のマイナス方向数値シーケンス生成子 |
| RangeTo | 指定した値まで(プラス方向、マイナス方向)の数値シーケンス生成子 |
| RepeatWithFinalize | 単一要素の無限リピート、列挙完了時にその要素を受け取る指定した関数を実行 |
| Return | IxのReturnと同じ |
| Scan | IxのScanと同じ |
| Share | IxのShareと同じ |
| Shuffle | シーケンスをランダム順に列挙する |
| TakeExceptLast | IxのSkipLastと同じ |
| TakeFromLast | IxのTakeLastと同じ |
| ToInfinity | 無限大までの数値シーケンス生成子 |
| ToJSON | シーケンスをJSON文字列に変換(組み込みのJSON関数のあるブラウザかjson2.jsの読み込みが必要) |
| ToNegativeInfinity | マイナス無限大までの数値シーケンス生成子 |
| ToObject | JSのオブジェクトに変換 |
| ToString | 文字列として値を連結 |
| Trace | console.logで値をモニタ |
| Unfold | Aggregateの逆、関数を連続適用する無限シーケンス生成子 |
| Write | document.writelnで値を出力 |
| WriteLine | document.writeln + <br />で値を出力 |
| TojQuery | シーケンスをjQueryオブジェクトに変換 |
| toEnumerable | jQueryの選択している複数の要素を単一要素のjQueryオブジェクトにしてEnumerableへ変換 |
| ToObservable | 引数のSchduler上で(デフォルトはCurrentThread)Observableへ変換 |
| ToEnumerable | Cold ObservableのみEnumerableへ変換 |
Ixと被るものもあれば、そうでもないものも。ToStringなどは分かりやすく便利でよく使うのではかと。ToJSONもいいですね。Fromは拡張メソッドのない/prototype汚染をしないための、JavaScriptだけのためのメソッド。Matchesは地味に便利です、JSの正規表現は使いやすいようでいて、マッチの列挙はかなり面倒くさいので、そこを解消してくれます。linq.jsは移植しただけ、ではあるんですが、同時に移植しただけではなくて、JavaScriptでLINQはどうあるべきか、どうあると便利なのか、という考えに基づいて調整されています。
JavaScriptにはyield returnがないので(Firefoxにはyieldありますが)、シーケンスは全て演算子の組み合わせだけで表現できなければならない。というのが、手厚くメソッドを用意している理由でもあります。これだけあれば何だって作れるでしょう、きっと多分恐らく。
まとめ
これで今日からLINQ to Objectsマスター。Rx版もそのうち書きます(以前にReactive Extensions入門 + メソッド早見解説表を書きましたが、今は結構変わってしまいましたからね)。