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では何がよく使われているのでしょうか。わたし、気になります

*1:追記:多分この記事を書いているときは匿名型を知らなかったんだと思います。今書くならタプルではなく匿名型を使いましょう。