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.

7voto

Vlad Radu Points 141
retourner new string(foo.Select(x => x).ToArray());

2voto

Abbondanza Points 800

J'ai effectué quelques tests dans LINQPad 7 (dotnet 6.0.1) avec BenchmarkDotNet :

Méthode

Moyenne

Erreur

Écart-type

StringFromArray

76.35 μs

1.482 μs

1.522 μs

StringConcat

100.93 μs

0.675 μs

0.631 μs

StringBuilder

100.52 μs

0.963 μs

0.901 μs

StringBuilderAggregate

116.80 μs

1.714 μs

1.519 μs

Code de test :

void Main() => BenchmarkRunner.Run();

public class CharsToString {
    private const int N = 10000;
    private readonly char[] data = new char[N];

    public CharsToString() {
        var random = new Random(42);
        for (var i = 0; i < data.Length; i++) {
            data[i] = (char)random.Next(0, 256);
        }
    }

    [Benchmark]
    public string StringFromArray()
        => new string(data.Where(char.IsLetterOrDigit).ToArray());

    [Benchmark]
    public string StringConcat()
        => string.Concat(data.Where(char.IsLetterOrDigit));

    [Benchmark]
    public string StringBuilder() {
        var sb = new StringBuilder();

        foreach (var c in data.Where(char.IsLetterOrDigit))
            sb.Append(c);

        return sb.ToString();
    }

    [Benchmark]
    public string StringBuilderAggregate() => data
        .Where(char.IsLetterOrDigit)
        .Aggregate(new StringBuilder(), (sb, c) => sb.Append(c))
        .ToString();
}

1voto

Joshcodes Points 1411

Cette réponse vise à combiner les aspects suivants des réponses déjà excellentes fournies.

  1. Lisible
  2. Prêt pour l'avenir / facile à refactoriser
  3. Rapide

Pour ce faire, une méthode d'extension sur IEnumerable est utilisée.

public static string Join(this IEnumerable chars)
{
#if NETCOREAPP2_1_OR_GREATER
    return String.Concat(chars);
#else
    var sb = new System.Text.StringBuilder();
    foreach (var c in chars)
    {
        sb.Append(c);
    }

    return sb.ToString();
#endif
}

Cela couvre toutes les bases.

  1. Il est très lisible:

    var y = x.TakeWhile(char.IsLetter).Join();

  2. S'il existe une nouvelle méthode préférée à l'avenir, toutes les conversions peuvent être mises à jour en changeant un bloc de code.

  3. Il prend en charge l'implémentation actuellement la plus performante en fonction de la version de .NET actuellement compilée.

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