IEnumerableに文字列連結

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があるなら文字列連結があってくれてもいいじゃない、みたいな。

Comment (4)

よこけん : (06/26 18:17)

誰か LINQ 版やってくれないかなぁと密かに思ってましたw
引数 deck が string で戻り値が string[] って固定されちゃってるのがキツいですね ^^;

neuecc : (06/30 02:22)

問題文が解答から逆算して作ったような感じで、自由度なかったですねー。
それでもこうして大勢の人が解いたのは、タイトルのつけかたが上手い煽りなのかしらん。

takekazuomi : (04/11 15:16)

今更ですが。
副作用を求めてSelectを使うと気持ち悪い、Select使うと新しいObjectが出来るのが気持ち悪い。諸々が重なって自分で書いたコードが気持ち悪いことになり、ググってたら見つけました。
とても、参考になりました。

ちょっとコメントを・・・

1.元のお題がわかっていないのかもしれないけど、Joinで良いのでは?

public static string Join(this IEnumerable source, string separator)
{
return string.Join(separator, source.Select(_ => _.ToString()).ToArray());
}

2.indexの代わりに、sb.Lengthを見れば最初かどうかは分かる気がします。

neuecc : (04/23 02:47)

1. .NET 4からは素敵なstring.Join(separator, IEnumerable)ができましたね!
2. 最初に空文字列が来る可能性があるのでsb.Lengthは使えないのではかと

Name
WebSite(option)
Comment

Trackback(0) | http://neue.cc/2009/06/24_170.html/trackback

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for .NET(C#)

April 2011
|
March 2017

Twitter:@neuecc
GitHub:neuecc
ils@neue.cc