リストの分割

// リスト自体は何だっていいので適当に生成
var list = Enumerable.Repeat("a", 50);
// 25以下をtrue、それ以上をfalseとするkeyでグループ分け
var result = list
	.Select((value, index) => new { value, index })
	.GroupBy(t => t.index <= 25, t => t.value);
// 多分、こんな感じに取り出す……?
var first = result.Single(g => g.Key == true);
var second = result.Single(g => g.Key == false);

2ch見てたら、要素数50のリストを0-25と26-50に分割するのどーするの、という質問が出てたのでごにゃごにゃ考えた。最初、list.GroupBy(i => i < 25);なんて答えたのだけど、即座に値じゃなくて添え字で分けたいんでしょーがボケ、と突っ込みが入ったので書き換えたのが↑のもの。微妙な長さと無駄手間感が何とも言えない、ただのLINQ遊びでしかない状態になってしまった。うーん。これはこれで面白いと思うのだけど。ただ、もう少し短くできないかな……?グループ化が挟まると、その後がどうしても長ったらしく。ていうか、あと、trueとfalseしかないんだから、First()とLast()でいいよね、という話ではある。

// 条件ばかりが無駄に多くて限りなく微妙
var alphabet = Enumerable.Range('A', 'z' + 1 - 'A')
	.Where(c => c <= 'Z' || c >= 'a')
	.GroupBy(c => c <= 'Z', c => (char)c);
// ていうか、意味ないよね、これ、全く
var upper = alphabet.First();
var lower = alphabet.Last();

何だか悔しいので、もう少し考えてみた。例えばアルファベットの大文字と小文字とか!……意味ない。死ぬほど意味がない。少なくとも、これだけを考えるならEnumerable.Range('a', 26).Select(i => ((char)i).ToString());でいいし。ダメだこりゃ。

追記

あ、違う、こういう場合はGroupByじゃなくてToLookup使えばいいんだった。GroupByと違ってキーでアクセス出来るようになるから、SingleだのFirstだのといった微妙なアクセスじゃなく(しかも、これらを使うと毎回走査してるってことだよね!) result[true]とかalphabet[true]とかでアクセス出来る。これで遙かにスッキリ。Lookupは素晴らしい。けど、ついつい忘れてしまう。

var result = list
	.Select((value, index) => new { value, index })
	.ToLookup(t => (t.index <= 25) ? "以下" : "より上", t => t.value);
var ika = result["以下"]; // こんな感じで
var tako = result["より上"]; // 取り出せるわけです!

これならすっきり。めでたしめでたし。キーは日本語でもboolでもenumでも好きなモノを使うといいさあー。

追追記

さっそく Enumerable.ToLookup が役立った - NyaRuRuの日記
つまりこういうことなのであった。間違いなく昔読んだはずなのに、すっかり忘れていたという事実が何よりも悲しい。あと、ToLookupで検索しても全然引っかからないというのが、限りなく人気無いメソッドのようで悲しい。私も忘れてたけどね!

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

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

Twitter:@neuecc GitHub:neuecc

Archive