VB.NETでもLINQでGroupByしたい
Group By
久々にLINQネタです。
今回はフィルタリングしつつ複数のキーでオブジェクトをグループ化しつつオブジェクト内の複数の要素の合計を個別に求めるときになんかこうスマートにできなかったのでそれについてです。
Module Module1 Sub Main() ' パターン1 ' 自分で射影する関数をGroupByに突っ込む方法 Dim result1 = Element.E. Where(Function(it) it.Key1 = "A" Or it.Key2 = "A"). GroupBy(Function(it) Tuple.Create(it.Key1, it.Key2), Function(k, it) Return New With { .Key1 = k.Item1, .Key2 = k.Item2, .Value1 = it.Sum(Function(e) e.Value1), .Value2 = it.Sum(Function(e) e.Value2), .Value3 = it.Sum(Function(e) e.Value3) } End Function) For Each it In result1 Console.WriteLine("{0} {1} {2} {3} {4}", it.Key1, it.Key2, it.Value1, it.Value2, it.Value3) Next Console.WriteLine() ' パターン2 ' 射影関数をSelectに突っ込んで頑張る方法 Dim result2 = Element.E.Where(Function(it) it.Key1 = "A" Or it.Key2 = "A"). GroupBy(Function(it) Tuple.Create(it.Key1, it.Key2)). Select(Function(it) New With { .Key1 = it.Key.Item1, .Key2 = it.Key.Item2, .Value1 = it.Sum(Function(h) h.Value1), .Value2 = it.Sum(Function(h) h.Value2), .Value3 = it.Sum(Function(h) h.Value3) }) For Each it In result2 Console.WriteLine("{0} {1} {2} {3} {4}", it.Key1, it.Key2, it.Value1, it.Value2, it.Value3) Next Console.WriteLine() ' パターン3 ' クエリ形式で何とか頑張る方法 Dim result3 = From e In Element.E Where e.Key1 = "A" Or e.Key2 = "A" Group By e.Key1, e.Key2 Into Group Select New With { .Key1 = Key1, .Key2 = Key2, .Value1 = Group.Sum(Function(it) it.Value1), .Value2 = Group.Sum(Function(it) it.Value2), .Value3 = Group.Sum(Function(it) it.Value3) } For Each it In result3 Console.WriteLine("{0} {1} {2} {3} {4}", it.Key1, it.Key2, it.Value1, it.Value2, it.Value3) Next End Sub End Module Class Element Public Shared E As Element() = { New Element With {.Key1 = "A", .Key2 = "A", .Value1 = 1, .Value2 = 2, .Value3 = 3}, New Element With {.Key1 = "A", .Key2 = "B", .Value1 = 4, .Value2 = 5, .Value3 = 6}, New Element With {.Key1 = "B", .Key2 = "A", .Value1 = 7, .Value2 = 8, .Value3 = 9}, New Element With {.Key1 = "A", .Key2 = "A", .Value1 = 1, .Value2 = 2, .Value3 = 3}, New Element With {.Key1 = "A", .Key2 = "B", .Value1 = 4, .Value2 = 5, .Value3 = 6}, New Element With {.Key1 = "B", .Key2 = "A", .Value1 = 7, .Value2 = 8, .Value3 = 9}, New Element With {.Key1 = "A", .Key2 = "A", .Value1 = 1, .Value2 = 2, .Value3 = 3}, New Element With {.Key1 = "A", .Key2 = "B", .Value1 = 4, .Value2 = 5, .Value3 = 6}, New Element With {.Key1 = "B", .Key2 = "A", .Value1 = 7, .Value2 = 8, .Value3 = 9}, New Element With {.Key1 = "B", .Key2 = "B", .Value1 = 1, .Value2 = 2, .Value3 = 3} } Public Property Key1 As String Public Property Key2 As String Public Property Value1 As Integer Public Property Value2 As Integer Public Property Value3 As Integer End Class
メソッド形式ではGroupBy
は
- キーとなる要素をぶっこぬく関数
- キーによってまとめられたオブジェクトを別のオブジェクトに射影するための関数
- キーを比較するためのオブジェクト
をそれぞれ使ったり使わなかったりするオーバーライドが8個あります。
キーにはタプルを利用しているので射影する関数を使うか使わないかのパターンを試しました。
といってもグループ化した後に何もしないでそのまま射影しちゃうので、ここでの例では違いはないです。
おまけ程度にクエリ形式で同じ動作をするはずのを書いてみました。 VBのクエリ形式は例がネットにもあまり多くないのでどう書けばいいのか今だに分からない事が多いです。
こういう書き方しかないかと思いますが、なんかこうもっとシンプルでスマートな書き方があるんじゃないかと思ってしまいます。 と言うかあると信じてます。
余談ですがこのような場合にタプルは便利です。 逆にオブジェクト間のデータのやりとりに使うと後から見直したりすると何が入っているか分からなくなり楽しいことになるので、できれば積極的に使用を推進してプロジェクトを崩壊に導きたいところですね。*1
さらに余談ですがVS2010ではラムダの中に20行くらい書くとなんだかとっても遅くなります。 そもそもラムダの中に20行も書くなといった感じですが、多分シンタックスチェックあたりが高負荷になってるのではないかと勝手に想像しています。
さらにさらに余談ですがラムダ式の引数名をit
にしてしまうのはGroovyのクロージャの癖が抜け切れていないからです。
.NETでは何がよく使われているのでしょうか。わたし、気になります。