webdevqa.jp.net

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

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

例えば:

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

IEnumerable<char>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関数の結果を再文字列化してパフォーマンスを最適化する方法があるかどうかです。

41
Shimmy

あなたが主にパフォーマンスを探していると仮定すると、このようなものはあなたの例のどれよりもかなり速いはずです:

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);
    }
}
15
LukeH

これを変換する方法はIEnumerable<char>string

string.Concat(x.TakeWhile(char.IsLetter));
45
Kai G

。Net Core 2.1のリリース用に編集されました

.Net Core 2.1のリリースに対してテストを繰り返すと、次のような結果が得られます

「Concat」の1000000回の反復には842msかかりました。

「新しい文字列」の1000000回の反復には1009ミリ秒かかりました。

「sb」の1000000回の反復には902ミリ秒かかりました。

つまり、.Net Core 2.1以降を使用している場合は、Concatが最適です。

詳細は MSブログ投稿 を参照してください。


私はこれを 別の質問 の主題にしましたが、ますます、それがこの質問に対する直接的な答えになっています。

IEnumerable<char>stringに変換する3つの簡単なメソッドのパフォーマンステストをいくつか実行しました。これらのメソッドは

新しい文字列

return new string(charSequence.ToArray());

連結

return string.Concat(charSequence)

StringBuilder

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

return sb.ToString();

私のテストでは、これは リンクされた質問 で詳しく説明されています。1000000"Some reasonably small test data"の反復では、次のような結果が得られます。

"Concat"の1000000回の反復には1597msかかりました。

「新しい文字列」の1000000回の反復には869msかかりました。

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

これは、このタスクにstring.Concatを使用する十分な理由がないことを私に示唆しています。単純化したい場合はnew stringアプローチを使用し、パフォーマンスが必要な場合はStringBuilder

私は私の主張を警告します、実際にはこれらすべての方法はうまく機能し、これはすべて最適化を超える可能性があります。

22
Jodrell

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

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

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

IEnumerable<char>を文字列に変換するより良い方法はありませんか?

(私の仮定は:)

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

これに対する唯一の解決策は、最初にバッキング配列またはStringBuilderにプッシュし、入力の増加に応じて再割り当てすることです。文字列のように低レベルの場合、これはおそらくメカニズムが隠されすぎていると考える必要があります。また、cannotができるだけ速くなるメカニズムを使用するように人々を促すことにより、パフォーマンスの問題を文字列クラスにプッシュします。

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

他の人が指摘したように、サポートコードを記述し、そのサポートコードを拡張メソッドでラップしてクリーンなインターフェイスを取得すれば、必要な結果(perf and表現力豊かなコード)を実現できます。

13

多くの場合、パフォーマンスを向上させることができます。しかし、それであなたは何を買いますか?これが実際のアプリケーションのボトルネックであり、それがそうであると測定していない限り、Linq TakeWhile()バージョンに固執します。これは、最も読みやすく保守しやすいソリューションであり、それが何よりも重要です。アプリケーション。

あなたがreallyで生のパフォーマンスを探している場合、手動で変換を行うことができます-以下は、私のテストではTakeWhile()よりも4倍以上(入力文字列の長さに応じて)高速でした-それが重要でない限り、私は個人的には使用しません。

int j = 0;
for (; j < input.Length; j++)
{
    if (!char.IsLetter(input[j]))
        break;
}
string output = input.Substring(0, j);
9
BrokenGlass
return new string(foo.Select(x => x).ToArray());
5
Vlad Radu