IEnumerable <char>を文字列に変換する最も良い方法は?

流暢な言語を string に使用できないのはなぜですか?

例えば:

var x = "asdf1234";
var y = new string(x.TakeWhile(char.IsLetter).ToArray());

Isn't there a better way to convert IEnumerable to string?

私が作ったテストはここにあります:

class Program
{
  static string input = "asdf1234";
  static void Main()
  {
    Console.WriteLine("1000 times:");
    RunTest(1000, input);
    Console.WriteLine("10000 times:");
    RunTest(10000,input);
    Console.WriteLine("100000 times:");
    RunTest(100000, input);
    Console.WriteLine("100000 times:");
    RunTest(100000, "ffff57467");


    Console.ReadKey();

  }

  static void RunTest( int times, string input)
  {

    Stopwatch sw = new Stopwatch();

    sw.Start();
    for (int i = 0; i < times; i++)
    {
      string output = new string(input.TakeWhile(char.IsLetter).ToArray());
    }
    sw.Stop();
    var first = sw.ElapsedTicks;

    sw.Restart();
    for (int i = 0; i < times; i++)
    {
      string output = Regex.Match(input, @"^[A-Z]+", 
        RegexOptions.IgnoreCase).Value;
    }
    sw.Stop();
    var second = sw.ElapsedTicks;

    var regex = new Regex(@"^[A-Z]+", 
      RegexOptions.IgnoreCase);
    sw.Restart();
    for (int i = 0; i < times; i++)
    {
      var output = regex.Match(input).Value;
    }
    sw.Stop();
    var third = sw.ElapsedTicks;

    double percent = (first + second + third)/100;
    double p1 = ( first/percent)/  100;
    double p2 = (second/percent )/100;
    double p3 = (third/percent  )/100;


    Console.WriteLine("TakeWhile took {0} ({1:P2}).,", first, p1);
    Console.WriteLine("Regex took {0}, ({1:P2})." , second,p2);
    Console.WriteLine("Preinstantiated Regex took {0}, ({1:P2}).", third,p3);
    Console.WriteLine();
  }
}

結果:

1000 times:
TakeWhile took 11217 (62.32%).,
Regex took 5044, (28.02%).
Preinstantiated Regex took 1741, (9.67%).

10000 times:
TakeWhile took 9210 (14.78%).,
Regex took 32461, (52.10%).
Preinstantiated Regex took 20669, (33.18%).

100000 times:
TakeWhile took 74945 (13.10%).,
Regex took 324520, (56.70%).
Preinstantiated Regex took 172913, (30.21%).

100000 times:
TakeWhile took 74511 (13.77%).,
Regex took 297760, (55.03%).
Preinstantiated Regex took 168911, (31.22%).

結論:私は何が良いのかを疑問視していますが、私は最初の実行時に最も遅い TakeWhile に行くつもりです。

とにかく、私の質問は、 TakeWhile 関数の結果を再編成してパフォーマンスを最適化する方法があるかどうかです。

30
@ルーク私はすでに何を選ぶべきか、決断を下した:最速。私の質問は、 new string(x.TakeWhile(p).ToArray)よりも良い方法がある場合です。
追加された 著者 Shimmy,
@Daveでは、基本関数を拡張メソッドでオーバーライドすることはできません。しかし、私は文字列 IEnumerable をとるコンストラクタです。
追加された 著者 Shimmy,
「最高」の意味を説明してください:最速ですか?メモリが空いている人はいませんか?簡単に理解できますか?
追加された 著者 LukeH,
@ルーク:あなたの解決策を元に戻したいと思うかもしれません:それは私よりもずっと速いです
追加された 著者 BrokenGlass,
これらの答えはすべて、なぜIEnumerable .ToString()がSystem.Linq.Enumerableでオーバーライドされていないのかを問う
追加された 著者 Dave,

6 答え

How about this to convert IEnumerable to string:

string.Concat(x.TakeWhile(char.IsLetter));
33
追加された
+1非常に短く、ToArray()を必要としません。
追加された 著者 Alex,
.Net 4.0のみ。あなたが独自の.TakeWhileを3.5に書いても、string.Concat(IEnumerable )は期待通りのことをしません。
追加された 著者 Dylan Nicholson,
私は、String.ConcatはStringBuilderを内部的に使用していると思います。それができなければ非常に奇妙であろう。だから、この解決法も本当にうまくいくはずです。
追加された 著者 Stefan Paul Noack,

.Net Core 2.1のリリース用に編集

.Net Core 2.1のリリースのためのテストを繰り返し、私はこのような結果を得る

「Concat」の1000000回の繰り返しに842msかかりました。

     

1000000回の「新しい文字列」の繰り返しは1009msでした。

     

"sb"の1000000回の反復には902msがかかりました。

要するに、.Net Core 2.1以降を使用している場合、 Concat は王様です。

MSのブログ投稿< a>を参照してください。


私はこれを

別の質問がありますが、それはこの質問への直接的な答えになりつつあります。

I've done some performance testing of 3 simple methods of converting an IEnumerable to a string, those methods are

新しい文字列

return new string(charSequence.ToArray());

Concat

return string.Concat(charSequence)

StringBuilder

var sb = new StringBuilder();
foreach (var c in charSequence)
{
    sb.Append(c);
}

return sb.ToString();

私のテストでは、それはリンクされた質問 "いくつかの合理的に小さなテストデータ "

「Concat」の1000000回の繰り返しは1597msでした。

     

1000000回の「新しい文字列」の繰り返しは869msでした。

     

"StringBuilder"の1000000回の反復には748msかかりました。

これは、このタスクに string.Concat を使用する正当な理由がないことを示唆しています。簡単にするには、新しい文字列アプローチを使用し、パフォーマンスを必要とする場合は StringBuilder を使用します。

私は自分の主張に注意し、実際にはこれらの方法はすべてうまく動作しますが、これはすべて最適化を超えている可能性があります。

15
追加された
StringBuilder を使用するコードを3行追加する代わりに、 new string を使用するには121ミリ秒を犠牲にしたいと考えています。 #cleanCode。
追加された 著者 RBT,

主にパフォーマンスを求めていると仮定すると、このようなものは、あなたの例よりもはるかに高速でなければなりません。

string x = "asdf1234";
string y = x.LeadingLettersOnly();

// ...

public static class StringExtensions
{
    public static string LeadingLettersOnly(this string source)
    {
        if (source == null)
            throw new ArgumentNullException("source");

        if (source.Length == 0)
            return source;

        char[] buffer = new char[source.Length];
        int bufferIndex = 0;

        for (int sourceIndex = 0; sourceIndex < source.Length; sourceIndex++)
        {
            char c = source[sourceIndex];

            if (!char.IsLetter(c))
                break;

            buffer[bufferIndex++] = c;
        }
        return new string(buffer, 0, bufferIndex);
    }
}
14
追加された
うーん、ちょうどあなたが文字列の先頭から文字が必要であることに気づいた。その場合、私は BrokenGlassの答えが最も速くなります。 (私は実際にベンチマークして確認していません)
追加された 著者 LukeH,
+1バッファの事前割り当てはおそらくこれを速くするものですが、これは単なる推測であり、限られたテストでは Substring()
追加された 著者 BrokenGlass,

文字列に流暢な言語を使用できないのはなぜですか?

可能です。あなたは質問自体でそれをやった:

var y = new string(x.TakeWhile(char.IsLetter).ToArray());

Isn't there a better way to convert IEnumerable to string?

(私の仮定は:)

文字列が不変なのでフレームワークにはこのようなコンストラクタがありません。文字列のメモリを事前に割り当てるには、列挙を2回トラバースする必要があります。これは常にオプションではありません。特に入力がストリームの場合は特にそうです。

これを解決する唯一の方法は、バッキング配列または StringBuilder を最初にプッシュし、入力が大きくなると再割り当てすることです。文字列のように低レベルのものについては、おそらくあまりにも隠されたメカニズムと考えるべきである。また、スピードクラスの問題を、できるだけ速くすることができないような仕組みを使用するよう促すことで、文字列クラスの問題を解決します。

これらの問題は、ユーザーに ToArray 拡張メソッドを使用させることで簡単に解決できます。

他にも指摘されているように、サポートコードを書いた場合、あなたが望むもの(perf 表現コード)を達成し、そのサポートコードを拡張メソッドでラップしてきれいなインターフェイスを得ることができます。

13
追加された
私は自分の拡張ライブラリに IEnumerable をとり、 string を返す Join オーバーロードを追加しました。コード>。
追加された 著者 Shimmy,
匿名のダウン者は何も助けません。あなたの理由を述べると、私はあなたの懸念に対処します。
追加された 著者 Merlyn Morgan-Graham,

あなたは非常に頻繁にパフォーマンスを向上させることができます。しかし、それがあなたを買うのは何ですか?これが本当にあなたのアプリケーションのボトルネックで、Linq TakeWhile()のバージョンに固執すると測定していない限り、これは最も読みやすくメンテナンス可能なソリューションです。すべてのアプリケーションのほとんど。

未処理のパフォーマンスを探しているのなら、 TakeWhile()よりも速く(入力文字列の長さによって)4倍以上の私のテスト - しかし、それが重要でない限り私は個人的に使用しません:

int j = 0;
for (; j < input.Length; j++)
{
    if (!char.IsLetter(input[j]))
        break;
}
string output = input.Substring(0, j);
9
追加された
関数は、検索クエリを数千(100000)文字列の最初の文字と比較することになっているため、パフォーマンスはすべて重要です。
追加された 著者 Shimmy,
@LukeH:その行は読みやすいですが、サポートコードは読みやすくありません。私はLinqがおそらく単にレビューをコードするのに対し、拡張メソッドのために多くの単体テストを書く必要があります。
追加された 著者 Merlyn Morgan-Graham,
+1。これを何らかのヘルパーメソッドでラップして再利用することは何も問題ありません。 source.LeadingLettersOnly()のようなものは、 new string(source.TakeWhile(char.IsLetter).ToArray())より読みやすくなります。
追加された 著者 LukeH,
@BrokenGlass:Ok、私は元に戻しました。私はまだベンチマークを実行していませんが、あなたのものを凌駕するよりも驚いています。私は、あなたが必要とするデータを高速でblitするために、 Substring がいくつかのネイティブコードを使用することを前提としていましたが、できるだけ。)
追加された 著者 LukeH,
@Merlyn:それは当てはまりますが、単体テストは once だけ書く必要があります。明らかに、私がパフォーマンスを必要としなかった場合は、毎回LINQバージョンに行くだろうが、OPは主な要件がパフォーマンスであることを強調した。
追加された 著者 LukeH,
@ルーク:あなたの解決方法は速いです - 元に戻してください!
追加された 著者 BrokenGlass,

return new string(foo.Select(x => x).ToArray());

4
追加された