49

流暢な言葉を使うことができないのはなぜ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とにかく、私の質問は、関数の結果を再ストリング化することによってパフォーマンスを最適化する方法があるかどうかです。

4

8 に答える 8

51

IEnumerable<char>これを変換するのはどうですかstring

string.Concat(x.TakeWhile(char.IsLetter));
于 2012-08-29T05:56:00.350 に答える
29

.NetCore2.1のリリース用に編集

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

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

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

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

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


私はこれを別の質問の主題にしましたが、ますます、それはこの質問への直接の答えになりつつあります。

をに変換する3つの簡単な方法のパフォーマンステストをいくつか行いましIEnumerable<char>string。これらの方法は次のとおりです。

新しい文字列

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回の反復には869ミリ秒かかりました。

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

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

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

于 2015-10-15T07:23:06.800 に答える
15

主にパフォーマンスを求めていると仮定すると、このようなものは、どの例よりも大幅に高速になるはずです。

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);
    }
}
于 2011-11-13T00:09:51.657 に答える
13

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

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

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

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

(私の仮定は:)

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

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

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

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

于 2011-11-13T02:47:57.070 に答える
9

多くの場合、パフォーマンスの面で優れたパフォーマンスを発揮できます。しかし、それはあなたに何を買いますか?これが本当にアプリケーションのボトルネックであり、LinqTakeWhile()バージョンに固執することを測定した場合を除いて、これは最も読みやすく保守しやすいソリューションであり、ほとんどすべてのアプリケーションで重要です。

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

int j = 0;
for (; j < input.Length; j++)
{
    if (!char.IsLetter(input[j]))
        break;
}
string output = input.Substring(0, j);
于 2011-11-13T00:13:32.403 に答える
7
return new string(foo.Select(x => x).ToArray());
于 2016-08-05T12:54:09.367 に答える
1

この回答は、すでに提供されている優れた回答の次の側面を組み合わせることを目的としています。

  1. 読みやすい
  2. 将来の保証/リファクタリングが簡単
  3. 速い

これを行うには、上の拡張メソッドIEnumerable<char>が使用されます。

public static string Join(this IEnumerable<char> chars)
{
#if NETCOREAPP2_1_OR_GREATER
    return String.Concat(chars);
#else
    var sb = new System.Text.StringBuilder();
    foreach (var c in chars)
    {
        sb.Append(c);
    }

    return sb.ToString();
#endif
}

これはすべての拠点をカバーしています。

  1. それは非常に読みやすいです:

    var y = x.TakeWhile(char.IsLetter).Join();

  2. 将来的に好ましい新しい方法がある場合は、コードの1つのブロックを変更することで、すべての変換を更新できます。

  3. 現在コンパイルされている.NETのバージョンに基づいて、現在最もパフォーマンスの高い実装をサポートします。

于 2021-04-22T17:44:36.093 に答える
0

BenchmarkDotNetを使用してLINQPad7(dotnet 6.0.1)でいくつかのテストを実行しました。

方法 平均 エラー StdDev
StringFromArray 76.35μs 1.482μs 1.522μs
StringConcat 100.93μs 0.675μs 0.631μs
StringBuilder 100.52μs 0.963μs 0.901μs
StringBuilderAggregate 116.80μs 1.714μs 1.519μs

テストコード:

void Main() => BenchmarkRunner.Run<CharsToString>();

public class CharsToString {
    private const int N = 10000;
    private readonly char[] data = new char[N];

    public CharsToString() {
        var random = new Random(42);
        for (var i = 0; i < data.Length; i++) {
            data[i] = (char)random.Next(0, 256);
        }
    }

    [Benchmark]
    public string StringFromArray()
        => new string(data.Where(char.IsLetterOrDigit).ToArray());

    [Benchmark]
    public string StringConcat()
        => string.Concat(data.Where(char.IsLetterOrDigit));

    [Benchmark]
    public string StringBuilder() {
        var sb = new StringBuilder();
        
        foreach (var c in data.Where(char.IsLetterOrDigit))
            sb.Append(c);
        
        return sb.ToString();
    }

    [Benchmark]
    public string StringBuilderAggregate() => data
        .Where(char.IsLetterOrDigit)
        .Aggregate(new StringBuilder(), (sb, c) => sb.Append(c))
        .ToString();
}
于 2022-01-26T11:54:30.583 に答える