Лучший способ конвертировать IEnumerable в string?

почему нельзя использовать беглый язык на 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 что медленнее только при первом запуске.

в любом случае, мой вопрос в том, есть ли способ оптимизировать производительность путем рестринга результата

6 ответов


предполагая, что вы ищете преимущественно производительность, то что-то вроде этого должно быть значительно быстрее, чем любой из ваших примеров:

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

как насчет этого, чтобы преобразовать IEnumerable<char> to string:

string.Concat(x.TakeWhile(char.IsLetter));

отредактировано для выпуска .Net Core 2.1

повторяя тест для выпуска .Net Core 2.1, я получаю такие результаты, как это

1000000 итераций "Concat" заняло 842ms.

1000000 итераций "новой строки"заняли 1009ms.

1000000 итераций " sb " заняло 902 МС.

короче говоря, если вы используете .Net Core 2.1 или более поздней версии,Concat - король.

посмотреть MS блог для более подробной информации.


Я сделал это предметом еще вопрос

я провел тестирование производительности 3 простых методов преобразования IEnumerable<char> до string, эти методы

новая строка

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();

в моем тестировании это подробно описано в связан вопрос, for 1000000 варианты "Some reasonably small test data" Я получаю такие результаты,

1000000 итераций "конкатенацию" взял 1597ms.

1000000 итераций "новой строки"заняло 869ms.

1000000 итераций "StringBuilder" взял 748ms.

это говорит мне о том, что нет веских причин использовать string.Concat для этой задачи. Если вы хотите простоту, используйте новая строка подход и если хотите производительности используйте StringBuilder.

Я бы предостерег свое утверждение, на практике все эти методы работают нормально, и все это может быть над оптимизацией.


почему нельзя использовать беглый язык в строке?

это возможно. Вы сделали это в самом вопросе:

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

нет ли лучшего способа конвертировать IEnumerable<char> в строку?

(мое предположение:)

платформа не имеет такого конструктора, потому что строки неизменяемы, и вам придется дважды пересечь перечисление, чтобы предварительно выделить память для строки. Этот это не всегда вариант, особенно если входной поток.

единственное решение этого-нажать на резервный массив или StringBuilder во-первых, и перераспределить по мере роста ввода. Для чего-то такого низкого уровня, как строка, это, вероятно, следует считать слишком скрытым механизмом. Это также подтолкнет проблемы perf вниз в класс string, поощряя людей использовать механизм, который не может быть как-быстро-как-можно.

эти проблемы легко решаются требование к пользователю использовать ToArray метод расширения.

как указывали другие, вы можете достичь того, чего хотите (perf и выразительный код), если вы пишете код поддержки и обертываете этот код поддержки в метод расширения, чтобы получить чистый интерфейс.


вы можете очень часто делать лучшую производительность. Но что это тебе даст? Если это действительно горлышко бутылки для вашего приложения, и вы измерили его, я бы придерживался Linq TakeWhile() версия: это наиболее читаемое и поддерживаемое решение, и это то, что считается для большинства приложений.

Если вы действительно ищут необработанную производительность, которую вы можете выполнить преобразование вручную - следующее было вокруг фактора 4+ (в зависимости от ввода длина строки) быстрее, чем TakeWhile() в моих тестах-но я бы не использовал его лично, если бы это не было критично:

int j = 0;
for (; j < input.Length; j++)
{
    if (!char.IsLetter(input[j]))
        break;
}
string output = input.Substring(0, j);

вернуть новую строку(foo.Выберите(х => х).ToArray ());