Лучший способ конвертировать IEnumerable <char>в строку? - PullRequest
33 голосов
/ 13 ноября 2011

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

Ответы [ 6 ]

39 голосов
/ 29 августа 2012

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

string.Concat(x.TakeWhile(char.IsLetter));
18 голосов
/ 15 октября 2015

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

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

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

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

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

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

Подробнее см. MS blog .


Я сделал это предметом другого вопроса , но все больше и больше это становится прямым ответом на этот вопрос.

Я провел тестирование производительности трех простых методов преобразования 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();

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

1000000 итераций "Concat" заняли 1597 мс.

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

1000000 итераций "StringBuilder" заняли 748 мс.

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

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

15 голосов
/ 13 ноября 2011

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

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);
    }
}
13 голосов
/ 13 ноября 2011

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

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

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

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

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

Фреймворк не имеет такого конструктора, потому что строки являются неизменяемыми, и вам придется дважды пройти по перечислению, чтобы предварительно выделить память для строки. Это не всегда вариант, особенно если ваш ввод - поток.

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

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

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

9 голосов
/ 13 ноября 2011

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

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

int j = 0;
for (; j < input.Length; j++)
{
    if (!char.IsLetter(input[j]))
        break;
}
string output = input.Substring(0, j);
5 голосов
/ 05 августа 2016
return new string(foo.Select(x => x).ToArray());
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...