La meilleure façon de convertir IEnumerable en string?

pourquoi n'est-il pas possible de parler couramment le string ?

par exemple:

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

n'y a-t-il pas une meilleure façon de convertir IEnumerable<char> en string ?

voici un test que j'ai fait:

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

résultat:

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%).

Conclusion: je doute de ce qui est mieux à préférer, je pense que je vais aller sur le TakeWhile qui est le plus lent seulement sur la première exécution.

quoi qu'il en soit, ma question Est s'il y a un moyen d'optimiser la performance en restrant le résultat de la fonction TakeWhile .

30
demandé sur Morten Kristensen 2011-11-13 03:19:49

6 réponses

en supposant que vous cherchez principalement la performance, alors quelque chose comme ça devrait être beaucoup plus rapide que n'importe lequel de vos exemples:

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
répondu LukeH 2011-11-13 02:30:01

Que Diriez-vous de cela pour convertir IEnumerable<char> en string :

string.Concat(x.TakeWhile(char.IsLetter));
35
répondu Kai G 2012-08-29 05:56:00

Édité pour la libération de l' .Net De Base 2.1

en répétant le test pour la libération de .net Core 2.1, j'obtiens des résultats comme celui-ci

1000000 itérations de "Concat" a pris 842ms.

1000000 itérations de "nouvelle chaîne" a pris 1009ms.

1000000 itérations de " sb " a pris 902ms.

en bref, si vous utilisez .net Core 2.1 ou plus tard, Concat est roi.

Voir MME blog pour plus de détails.


j'ai fait cela le sujet de une autre question mais de plus en plus, qui devient une réponse directe à cette question.

j'ai fait quelques tests de performance de 3 méthodes simples de conversion d'un IEnumerable<char> en un string , ces méthodes sont

nouvelle chaîne

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

dans mon test, qui est détaillé dans la question liée , pour 1000000 itérations de "Some reasonably small test data" J'obtiens des résultats comme celui-ci,

1000000 itérations de "Concat" a pris 1597ms.

1000000 itérations de "nouvelle chaîne" a pris 869ms.

1000000 itérations de "StringBuilder" a pris 748ms.

cela me porte à croire qu'il n'y a pas de bonne raison d'utiliser string.Concat pour cette tâche. Si vous voulez la simplicité utiliser la nouvelle chaîne de caractères approche et si vous voulez performance utiliser le StringBuilder .

je voudrais mettre en garde mon affirmation, dans la pratique toutes ces méthodes fonctionnent bien, et tout cela pourrait être sur l'optimisation.

16
répondu Jodrell 2018-05-31 12:32:54

pourquoi n'est-il pas possible d'utiliser un langage courant sur une chaîne de caractères?

c'est possible. Vous l'avez fait dans la question elle-même:

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

n'y a-t-il pas une meilleure façon de convertir IEnumerable<char> en chaîne?

(mon hypothèse est:)

le framework n'a pas un tel constructeur parce que les chaînes sont immuables, et vous devez traverser le énumérer deux fois afin de pré-allouer la mémoire pour la chaîne. Ce n'est pas toujours une option, surtout si votre entrée est un flux.

la seule solution à cela est de pousser sur un tableau de soutien ou StringBuilder d'abord, et de réallouer au fur et à mesure que l'entrée augmente. Pour quelque chose d'aussi bas niveau qu'une chaîne, cela devrait probablement être considéré comme un mécanisme trop caché. Il pousserait également les problèmes perf vers le bas dans la classe de corde en encourageant les gens à utiliser un mécanisme qui ne peut pas être aussi rapide que possible.

Ces problèmes sont résolus facilement en demandant à l'utilisateur d'utiliser le ToArray méthode d'extension.

comme d'autres l'ont souligné, vous pouvez atteindre ce que vous voulez (perf et code expressif) si vous écrivez le code de soutien, et envelopper ce code de soutien dans une méthode d'extension pour obtenir une interface propre.

13
répondu Merlyn Morgan-Graham 2011-11-13 02:53:01

vous pouvez très souvent faire de meilleures performances. Mais que faut-il acheter de vous? A moins que ce ne soit vraiment le goulot de la bouteille pour votre application et que vous l'ayez mesuré, Je m'en tiendrais à la version Linq TakeWhile() : c'est la solution la plus lisible et maintenable, et c'est ce qui compte pour la plupart des applications.

si vous vraiment êtes à la recherche de la performance brute, vous pouvez faire la conversion manuellement - ce qui suit était environ un facteur 4+ (dépendant de la longueur de la chaîne de caractères) plus rapide que TakeWhile() dans mes tests - mais je ne l'utiliserais pas personnellement à moins que ce ne soit critique:

int j = 0;
for (; j < input.Length; j++)
{
    if (!char.IsLetter(input[j]))
        break;
}
string output = input.Substring(0, j);
9
répondu BrokenGlass 2011-11-13 00:13:32

return new string (foo.Sélectionnez (x => x).ToArray ());

4
répondu Vlad Radu 2016-08-05 12:54:09