LINQとeach_sliceと匿名Enum

ToLookupで思ったのは、匿名Enumが欲しい。わざわざ外に定義するほどでもないけれど、ただの文字列で区分けするのは嫌だ。なんて思ったのは、以前ジオメトリのランキングを引っ張ってくる際にGroupByを使った時に思ったんだった。それにしても、改めて眺めてみると酷い、Firstの連発とか。しかし、昔とは違うんです、昔とは。冷静に問題を考えればきっと分かる。

ようするに配列を3つ刻みで分割したい。{3, 4, 631, 671, 7, 5, 82, 1, 2}とあったら、{[3,4,631],[671,7,5],[82,1,2]}に分けたい。一次元→二次元、ということかな?phpのarray_chunkやRubyのEnumerable#each_sliceがそれに相当する、っぽい。

しかし延々と考えてもさっぱり思い浮かばない。自分で思い浮かばない時は検索しよう、と検索したらすぐ出てきた。Eric White's Blog : Chunking a Collection into Groups of Three。 3つ区切りでグループ分け、とはまさに考えている問題と同じ話。ただ、あの、このFirstの連打は私とやってること同じじゃん。

私のようなタコならともかく、他の人も似たようなことやってるし、もしくは拡張メソッドでループ回している例しか見つからないから、しょうがないかな、と諦め。きれない。ようはSkip().First()にせよFirst(predicate)にせよ、IEnumerable<T>状態だからいけない。何らかのキーで直接アクセス出来ればいいわけですよね? お、ToLookupぢゃん!でも今回は中に入るのが一個であること確定なので、ToDictionaryが良いですね。そして、ふふ、滅多に出番のないGroupByのresultSelectorを使うときがついにきたようだ……。

// 3つ区切りで、[商品名,値段,売ってる場所]となっている配列を分解したい
string[] array = { "大根", "100", "八百屋", "豚肉", "300", "肉屋", "イカ", "150", "魚屋" };

// enumモドキの匿名型
var Key = new
{
    Name = Guid.NewGuid(),
    Price = Guid.NewGuid(),
    Shop = Guid.NewGuid()
};

var result = array.Select((value, index) => new
    {
        value,
        chunk = index / 3,
        attr = (index % 3 == 0) ? Key.Name :
               (index % 3 == 1) ? Key.Price :
               Key.Shop
    }).GroupBy(t => t.chunk, t => t, (k, g) => g.ToDictionary(t => t.attr, t => t.value))
    .Select(d => new
    {
        Name = d[Key.Name],
        Price = d[Key.Price],
        Shop = d[Key.Shop]
    });

できたー。最初のSelect時にバラしているので行数がSkip().First()より増えているのは突っ込みどころだけど、満足。あと、元の配列がきっかり3で割りきれないと、Dictionaryなので存在しないキーにアクセスしました例外が出て死んでしまいます。対策としてはToLookupにして[Key.Name].FirstOrDefault()を使う、というのがすぐに浮かんだけど、スッキリしないのは否めない。ていうかその辺も踏まえてもSkip().FirstOrDefault()のほうが賢いやり方な気がする……。

で、冒頭の匿名Enum云々は、匿名型を使って実現?してみました。値は、絶対に被らないもの、ということでGUIDを使ってみた。ただ、定義時に全部にGuid.NewGuid()というのがダルい。というか若干ヤケくそ気味なのは否めない。こちらも上手いやり方が思い浮かばず、ってそればっか。そもそもEnumモドキなら、intでいいし、もしくは"商品名"とか文字列を使えば普通に便利なので、GUIDは全く意味ないですね。しょうもな。というわけで、実際に使う場合(あるかなあ?)はGUIDじゃなくて文字列でやります……。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive