60 votes

Meilleure façon de convertir IEnumerable<char> en une chaîne de caractères ?

Pourquoi n'est-il pas possible d'utiliser une langue fluide sur chaîne?

Par exemple :

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

N'y a-t-il pas un meilleur moyen de convertir IEnumerable en chaîne?

Voici un test que j'ai réalisé :

class Program
{
  static string input = "asdf1234";
  static void Main()
  {
    Console.WriteLine("1000 fois :");
    RunTest(1000, input);
    Console.WriteLine("10000 fois :");
    RunTest(10000,input);
    Console.WriteLine("100000 fois :");
    RunTest(100000, input);
    Console.WriteLine("100000 fois :");
    RunTest(100000, "ffff57467");

    Console.ReadKey();

  }

  static void RunTest( int fois, string input)
  {

    Stopwatch sw = new Stopwatch();

    sw.Start();
    for (int i = 0; i < fois; i++)
    {
      string output = new string(input.TakeWhile(char.IsLetter).ToArray());
    }
    sw.Stop();
    var first = sw.ElapsedTicks;

    sw.Restart();
    for (int i = 0; i < fois; 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 < fois; i++)
    {
      var output = regex.Match(input).Value;
    }
    sw.Stop();
    var third = sw.ElapsedTicks;

    double pourcentage = (first + second + third) / 100;
    double p1 = ( first / pourcentage)/  100;
    double p2 = (second / pourcentage )/100;
    double p3 = (third / pourcentage  )/100;

    Console.WriteLine("TakeWhile a pris {0} ({1:P2}).,", first, p1);
    Console.WriteLine("Regex a pris {0}, ({1:P2})." , second,p2);
    Console.WriteLine("Regex pré-instantié a pris {0}, ({1:P2}).", third,p3);
    Console.WriteLine();
  }
}

Résultat :

1000 fois :
TakeWhile a pris 11217 (62,32%).,
Regex a pris 5044, (28,02%).
Regex pré-instantié a pris 1741, (9,67%).

10000 fois :
TakeWhile a pris 9210 (14,78%).,
Regex a pris 32461, (52,10%).
Regex pré-instantié a pris 20669, (33,18%).

100000 fois :
TakeWhile a pris 74945 (13,10%).,
Regex a pris 324520, (56,70%).
Regex pré-instantié a pris 172913, (30,21%).

100000 fois :
TakeWhile a pris 74511 (13,77%).,
Regex a pris 297760, (55,03%).
Regex pré-instantié a pris 168911, (31,22%).

Conclusion : Je doute de ce qu'il est préférable de privilégier, je pense que je vais choisir le TakeWhile qui est le plus lent uniquement lors de la première exécution.

Quoi qu'il en soit, ma question est de savoir s'il existe un moyen d'optimiser les performances en restreignant le résultat de la fonction TakeWhile.

57voto

Kai G Points 1203

Que diriez-vous de convertir IEnumerable en string:

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

31voto

Jodrell Points 14205

Modifié pour la sortie de .Net Core 2.1

En répétant le test pour la sortie de .Net Core 2.1, j'obtiens des résultats comme ceci

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

1000000 itérations de "new String" ont pris 1009ms.

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

En résumé, si vous utilisez .Net Core 2.1 ou ultérieur, Concat est roi.


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

J'ai effectué des tests de performances de 3 méthodes simples pour convertir un IEnumerable en 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 mes tests, qui sont détaillés dans la question liée, pour 1000000 itérations de "Certains données de test raisonnablement petites" J'obtiens des résultats comme ceci,

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

1000000 itérations de "new string" ont pris 869ms.

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

Cela me suggère qu'il n'y a pas de bonne raison d'utiliser string.Concat pour cette tâche. Si vous voulez simplicité, utilisez l'approche de nouvelle chaîne et si vous voulez des performances, utilisez le StringBuilder.

Je nuancerais mon affirmation, en pratique toutes ces méthodes fonctionnent bien, et tout cela pourrait être une sur-optimisation.

15voto

LukeH Points 110965

En supposant que vous cherchez principalement la performance, alors quelque chose comme ceci devrait être sensiblement 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);
    }
}

13voto

Merlyn Morgan-Graham Points 31815

Pourquoi n'est-il pas possible d'utiliser un langage fluide sur une chaîne?

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 un moyen meilleur de convertir IEnumerable en chaîne?

(Mon hypothèse est:)

Le framework n'a pas de tel constructeur car les chaînes sont immuables, et vous devriez traverser l'énumération 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 vers un tableau de sauvegarde ou un 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é. Cela entraînerait également des problèmes de performances dans la classe de chaînes en encourageant les gens à utiliser un mécanisme qui ne peut pas être aussi rapide que possible.

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

Comme d'autres l'ont souligné, vous pouvez obtenir ce que vous voulez (performance et code expressif) si vous écrivez du code de support, et enveloppez ce code de support dans une méthode d'extension pour obtenir une interface propre.

9voto

BrokenGlass Points 91618

Vous pouvez très souvent obtenir de meilleures performances. Mais qu'est-ce que cela vous apporte ? À moins que ce ne soit vraiment le goulot d'étranglement de votre application et que vous l'ayez mesuré, je resterais avec la version Linq TakeWhile() : c'est la solution la plus lisible et la plus maintenable, et c'est ce qui compte pour la plupart des applications.

Si vous cherchez vraiment la performance brute, vous pourriez faire la conversion manuellement - ce qui était environ 4+ fois plus rapide (selon la longueur de la chaîne en entrée) que TakeWhile() dans mes tests - mais je ne l'utiliserais personnellement pas sauf si c'était critique:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X