IEnumerableに文字列連結
- 2009-06-24
C#と諸々 10分でコーディングから。ネタ元、のネタ元の問題文の解読しづらさは異常。例も酷かった。というのはともかく、Linqならグルーピングのお話だよねー
class Cards
{
public string[] Deal(int numPlayers, string deck)
{
var count = deck.Length / numPlayers;
var result = deck
.Select((c, i) => new { c, i })
.GroupBy(t => (t.i % numPlayers), t => t.c)
.Select(g => new String(g.Select(c => c).ToArray()))
.Select(s => (s.Length > count) ? s.Substring(0, count) : s)
.ToArray();
return (result.Length < numPlayers)
? Enumerable.Repeat("", numPlayers).ToArray()
: result;
}
}
あれ、スッキリどころか意外と汚い……。んー、最初はGroupByのところがToLookupでそのままreturnしてすっきり終了。だったのですが、「求められるのはIGroupingじゃなくて文字列」なのでLookupじゃなくGroupByにして後段のSelectで結果を文字列化。「余ったカードは配られず捨てられる」ので更に後段のSelectで廃棄処分。「カードが足りなかった場合は人数分の空文字列配列を返す」のでreturnを別に分けてRepeatでそれを生成。後付けでごにゃごにゃ足していった結果、汚くなってしまった、残念。
まあでも、何が嫌かってnew Stringの部分ですね。中身がcharじゃなくて文字列の時はstring.Joinを使いますが、どちらもToArrayがウザくなってイヤー。IEnumerableにToStringは文字列で連結して欲しい。いや、Aggregateで代替出来るのは知ってますが定型処理のくせに必要なタイプ数多くてnew Stringとあんま変わらないし、セパレータも付けたいとなるといよいよ面倒くさい。というわけで拡張メソッドの出番。
public static class Ext
{
public static string ToJoinedString<T>(this IEnumerable<T> source)
{
return source.ToJoinedString("");
}
public static string ToJoinedString<T>(this IEnumerable<T> source, string separator)
{
var index = 0;
return source.Aggregate(new StringBuilder(),
(sb, o) => (index++ == 0) ? sb.Append(o) : sb.AppendFormat("{0}{1}", separator, o))
.ToString();
}
}
残念ながら拡張メソッドは元からあるメソッド名を上書きできないのでToStringではなくToJoinedString。関数名が微妙? そうですね……。 セパレータを使いたい場合にAggregateでサクッと書けなくて昔悩んだのですが、カウント用の変数を場外に用意すればいいんだ!と気付いたのでサクッと書けるようになりました。Linqだけで1行で済ませたい場合はSelectでindex作ってやればいいですねー。
var result = Enumerable.Repeat("hoge", 10)
.Select((s, i) => new { s, i })
.Aggregate(new StringBuilder(), (sb, t) => (t.i == 0)
? sb.Append(t.s)
: sb.AppendFormat("{0}{1}", "|", t.s));
悪夢だ。実行効率的にもアレゲ。いくらAggregateで何でも出来るといっても、この辺は標準搭載して欲しいものですねー、Sumがあるなら文字列連結があってくれてもいいじゃない、みたいな。