読者です 読者をやめる 読者になる 読者になる

VB.NETでもLinqでGroupByしたい

あっちの進捗

ここ数回の記事を見返してサブカルネタを多用しすぎではないかと一人反省している弊社です。 今回は使わないように頑張ります。

どうもエンコーダ・デコーダはCOM連携としてポインタを引数としてとるメソッドも定義しないといけないみたいで、現在は.NETでのポインタの扱いについて確認している次第でございます。 そもそもCOMとはなんぞやという状態の弊社としてはお勉強しなくてはならない事が多いです。

とりあえずCLSに準拠してないメソッドについてはオーバーライドするだけしてNotSupportedExceptionをぶん投げてしまえばいい気もしますが、せっかくなんでポインタ周りを軽く触れてみようと思ってます。*1

Group By

久々にLinqネタです。

といっても弊社はLinq書けない星人なのでC#もとい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個あります。*2 キーにはタプルを利用しているので射影する関数を使うか使わないかのパターンを試しました。

といってもグループ化した後に何もしないでそのまま射影しちゃうので意味上では違いはないです。

おまけ程度にクエリ形式で同じ動作をするはずのを書いてみました。 VBのクエリ形式は例がネットにもあまり多くないのでどう書けばいいのか今だに分からない事が多いです。

こういう書き方しかないかと思いますが、なんかこうもっとシンプルでスマートな書き方があるんじゃないかと思ってしまいます。 と言うかあると信じてます。

余談ですがこのような場合にタプルは便利です。 逆にオブジェクト間のデータのやりとりに使うと後から見直したりすると何が入っているか分からなくなり楽しいことになるので、できれば積極的に使用を推進してプロジェクトを崩壊に導きたいところですね。

さらに余談ですがVS2010ではラムダの中に20行くらい書くとなんだかとっても遅くなります。 そもそもラムダの中に20行も書くなといった感じですが、多分シンタックスチェックあたりが高負荷になってるのではないかと勝手に想像しています。

さらにさらに余談ですがラムダ式の引数名をitにしてしまうのはGroovyのクロージャの癖が抜け切れていないからです。 .NETでは何がよく使われているのでしょうか。わたし、気になります*3

*1:記事として書くとは言ってない

*2:関数とかオブジェクトとかの言葉の使い分けは厳密にしている訳ではないく、いい感じかつテキトーに使っています。

*3:抑えられなかった