Лучший способ конвертировать 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.

Ответ 1

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

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

Ответ 2

Как об этом конвертировать IEnumerable<char> в string:

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

Ответ 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();

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

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

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

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

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

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

Ответ 4

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

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

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

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

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

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

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

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

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

Ответ 5

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

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

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

Ответ 6

возвращает новую строку (foo.Select(x = > x).ToArray());