Utilisation de LINQ pour concaténer des chaînes

346

Quelle est la façon la plus efficace d'écrire à l'ancienne:

StringBuilder sb = new StringBuilder();
if (strings.Count > 0)
{
    foreach (string s in strings)
    {
        sb.Append(s + ", ");
    }
    sb.Remove(sb.Length - 2, 2);
}
return sb.ToString();

... dans LINQ?

tags2k
la source
1
Avez-vous découvert d'autres façons de faire les choses super sympas LINQ?
Robert
3
Eh bien, la réponse sélectionnée et toutes les autres options ne fonctionnent pas dans Linq to Entities.
Binoj Antony
3
@Binoj Antony, ne faites pas concaténer vos chaînes de base de données.
Amy B
6
@ Pr0fess0rX: Parce qu'il ne peut pas et parce qu'il ne devrait pas. Je ne connais pas les autres bases de données mais dans SQL Server, vous ne pouvez concat (n) varcahr que vous limiter à (n) varchar (max). Cela ne devrait pas être le cas, car la logique métier ne doit pas être implémentée dans la couche de données.
the_drow
une solution finale avec code source complet et hautes performances?
Kiquenet

Réponses:

529

Cette réponse montre l'utilisation de LINQ ( Aggregate) comme demandé dans la question et n'est pas destinée à un usage quotidien. Parce que cela n'utilise pas un, StringBuilderil aura des performances horribles pour de très longues séquences. Pour une utilisation régulière du codeString.Join comme indiqué dans l'autre réponse

Utilisez des requêtes agrégées comme celle-ci:

string[] words = { "one", "two", "three" };
var res = words.Aggregate(
   "", // start with empty string to handle empty list case.
   (current, next) => current + ", " + next);
Console.WriteLine(res);

Cela produit:

, un deux trois

Un agrégat est une fonction qui prend une collection de valeurs et renvoie une valeur scalaire. Les exemples de T-SQL incluent min, max et sum. VB et C # prennent en charge les agrégats. VB et C # prennent en charge les agrégats en tant que méthodes d'extension. En utilisant la notation par points, on appelle simplement une méthode sur un IEnumerable objet .

N'oubliez pas que les requêtes agrégées sont exécutées immédiatement.

Plus d'informations - MSDN: requêtes agrégées


Si vous voulez vraiment utiliser Aggregateuse variant using StringBuilderpropose en commentaire par CodeMonkeyKing qui serait à peu près le même code que regular String.Joinincluant de bonnes performances pour un grand nombre d'objets:

 var res = words.Aggregate(
     new StringBuilder(), 
     (current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
     .ToString();
Jorge Ferreira
la source
4
Le premier exemple ne génère pas "un, deux, trois", il génère ", un, deux, trois" (remarquez la virgule principale).
Mort
Dans votre premier exemple, puisque vous amorcez avec "", la première valeur utilisée dans currentest une chaîne vide. Ainsi, pour 1 ou plusieurs éléments, vous obtiendrez toujours , au début de la chaîne.
Michael Yanni
@Mort J'ai résolu ce
problème
358
return string.Join(", ", strings.ToArray());

Dans .Net 4, il y a une nouvelle surcharge pour string.Joincela accepte IEnumerable<string>. Le code ressemblerait alors à:

return string.Join(", ", strings);
Amy B
la source
2
D'accord, donc la solution n'utilise pas Linq, mais cela semble assez bien fonctionner pour moi
Mat Roberts
33
ToArray est linq :)
Amy B
18
C'est la réponse la plus correcte. Il est plus rapide que la question et la réponse acceptée et est beaucoup plus clair que l'agrégat, qui nécessite une explication d'un paragraphe à chaque fois qu'il est utilisé.
PRMan
@realPro Complètement faux. github.com/microsoft/referencesource/blob/master/mscorlib/… ligne 161
Amy B
125

Pourquoi utiliser Linq?

string[] s = {"foo", "bar", "baz"};
Console.WriteLine(String.Join(", ", s));

Cela fonctionne parfaitement et accepte tout IEnumerable<string>autant que je me souvienne. Pas besoin de Aggregatequoi que ce soit ici qui est beaucoup plus lent.

Armin Ronacher
la source
19
Apprendre LINQ peut être cool, et LINQ peut être un moyen mignon d'accomplir la fin, mais utiliser LINQ pour obtenir réellement le résultat final serait pour le moins mauvais, sinon carrément stupide
Jason Bunting
9
.NET 4.0 a une surcharge IEnumerable <string> et IEnumrable <T>, ce qui le rendra beaucoup plus facile à utiliser
Cine
3
Comme le souligne Cine, .NET 4.0 a la surcharge. Les versions précédentes ne le font pas. Vous pouvez toujours le faire String.Join(",", s.ToArray())dans les anciennes versions.
Martijn
1
FYI: fusionné de stackoverflow.com/questions/122670/…
Shog9
@ La fusion de Shog9 fait que les réponses ici ressemblent à des efforts dupliqués, et les horodatages n'aident pas du tout .. Toujours le chemin à parcourir.
nawfal
77

Avez-vous examiné la méthode d'extension d'agrégat?

var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);
Robert S.
la source
23
C'est probablement plus lent que String.Join () et plus difficile à lire dans le code. Répond à la question pour une "façon LINQ", cependant :-)
Chris Wenham
5
Ouais, je ne voulais pas entacher la réponse de mes opinions. : P
Robert S.
2
C'est incontestablement un peu plus lent, en fait. Même utiliser Aggregate avec un StringBuilder au lieu de la concaténation est plus lent que String.Join.
Joel Mueller
4
A fait un test avec 10 000 000 itérations, l'agrégat a pris 4,3 secondes et string.join a pris 2,3 secondes. Je dirais donc que le diff diff n'est pas important pour 99% des cas d'utilisation courants. Donc, si vous faites déjà beaucoup de linq pour traiter vos données, il n'est généralement pas nécessaire de casser cette belle syntaxe et d'utiliser string.join imo. gist.github.com/joeriks/5791981
joeriks
1
FYI: fusionné de stackoverflow.com/questions/122670/…
Shog9
56

Exemple réel de mon code:

return selected.Select(query => query.Name).Aggregate((a, b) => a + ", " + b);

Une requête est un objet qui a une propriété Name qui est une chaîne, et je veux les noms de toutes les requêtes de la liste sélectionnée, séparés par des virgules.

Daniel Earwicker
la source
2
Compte tenu des commentaires sur les performances, je dois ajouter que l'exemple provient d'un code qui s'exécute une fois lorsqu'une boîte de dialogue se ferme et qu'il est peu probable que la liste contienne plus d'une dizaine de chaînes!
Daniel Earwicker
Une idée de comment faire cette même tâche dans Linq to Entities?
Binoj Antony
1
Excellent exemple. Merci d'avoir mis cela dans un scénario réel. J'avais la même situation exacte, avec une propriété d'un objet qui devait être concassée.
Jessy Houle
1
A voté pour m'aider à comprendre cette première partie de la sélection de la propriété de chaîne de ma liste <T>
Nikki9696
1
Veuillez écrire sur les performances de cette approche avec un plus grand tableau.
Giulio Caccin
31

Voici l'approche combinée Join / Linq sur laquelle j'ai opté après avoir examiné les autres réponses et les problèmes abordés dans une question similaire (à savoir que l'agrégation et la concaténation échouent avec 0 élément).

string Result = String.Join(",", split.Select(s => s.Name));

ou (si ce sn'est pas une chaîne)

string Result = String.Join(",", split.Select(s => s.ToString()));

  • Facile
  • facile à lire et à comprendre
  • fonctionne pour les éléments génériques
  • permet d'utiliser des objets ou des propriétés d'objet
  • gère le cas des éléments de longueur 0
  • pourrait être utilisé avec un filtrage Linq supplémentaire
  • fonctionne bien (au moins d'après mon expérience)
  • ne nécessite pas la création (manuelle) d'un objet supplémentaire (par exemple StringBuilder) à implémenter

Et bien sûr, Join s'occupe de la virgule finale embêtante qui se glisse parfois dans d'autres approches ( for, foreach), c'est pourquoi je cherchais une solution Linq en premier lieu.

brichins
la source
1
parenthèse appariée.
ctrl-alt-delor
1
FYI: fusionné de stackoverflow.com/questions/122670/…
Shog9
3
J'aime cette réponse parce que l'utilisation .Select()comme celle-ci fournit un endroit facile pour modifier chaque élément pendant cette opération. Par exemple, en emballant chaque élément dans un caractère comme celui-cistring Result = String.Join(",", split.Select(s => "'" + s + "'"));
Sam Storie
29

Vous pouvez utiliser StringBuilderdans Aggregate:

  List<string> strings = new List<string>() { "one", "two", "three" };

  StringBuilder sb = strings
    .Select(s => s)
    .Aggregate(new StringBuilder(), (ag, n) => ag.Append(n).Append(", "));

  if (sb.Length > 0) { sb.Remove(sb.Length - 2, 2); }

  Console.WriteLine(sb.ToString());

(Le Selectest là juste pour montrer que vous pouvez faire plus de choses LINQ.)

jonathan.s
la source
2
+1 sympa. Cependant, OMI, il vaut mieux éviter d'ajouter le "" supplémentaire que de l'effacer par la suite. Quelque chose commenew[] {"one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) =>{if (sb.Length > 0) sb.Append(", ");sb.Append(s);return sb;}).ToString();
dss539
5
Vous économiseriez de précieux cycles d'horloge en ne vérifiant pas if (length > 0)le linq et en le retirant.
Binoj Antony
1
Je suis d'accord avec dss539. Ma version va dans le sens denew[] {"", "one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) => (String.IsNullOrEmpty(sb.ToString())) ? sb.Append(s) : sb.Append(", ").Append(s)).ToString();
ProfNimrod
22

données de performances rapides pour le cas StringBuilder vs Select & Aggregate sur 3000 éléments:

Test unitaire - Durée (secondes)
LINQ_StringBuilder - 0,0036644
LINQ_Select.Aggregate - 1,8012535

    [TestMethod()]
    public void LINQ_StringBuilder()
    {
        IList<int> ints = new List<int>();
        for (int i = 0; i < 3000;i++ )
        {
            ints.Add(i);
        }
        StringBuilder idString = new StringBuilder();
        foreach (int id in ints)
        {
            idString.Append(id + ", ");
        }
    }
    [TestMethod()]
    public void LINQ_SELECT()
    {
        IList<int> ints = new List<int>();
        for (int i = 0; i < 3000; i++)
        {
            ints.Add(i);
        }
        string ids = ints.Select(query => query.ToString())
                         .Aggregate((a, b) => a + ", " + b);
    }
user337754
la source
Utile pour décider d'emprunter la voie non LINQ pour cela
crabCRUSHERclamCOLLECTOR
4
La différence de temps est probablement StringBuilder vs String Concatination en utilisant +. Rien à voir avec LINQ ou Aggregate. Mettez StringBuilder dans LINQ Aggregate (de nombreux exemples sur SO), et cela devrait être tout aussi rapide.
controlbox
16

J'utilise toujours la méthode d'extension:

public static string JoinAsString<T>(this IEnumerable<T> input, string seperator)
{
    var ar = input.Select(i => i.ToString()).ToArray();
    return string.Join(seperator, ar);
}
Kieran Benton
la source
5
string.Joindans .net 4 peut déjà prendre un IEnumerable<T>pour tout arbitraire T.
récursif
1
FYI: fusionné de stackoverflow.com/questions/122670/…
Shog9
12

Par « voie LINQ super cool », vous pourriez parler de la façon dont LINQ rend la programmation fonctionnelle beaucoup plus acceptable avec l'utilisation de méthodes d'extension. Je veux dire, le sucre syntaxique qui permet d'enchaîner les fonctions de manière visuellement linéaire (l'une après l'autre) au lieu de s'emboîter (l'une dans l'autre). Par exemple:

int totalEven = Enumerable.Sum(Enumerable.Where(myInts, i => i % 2 == 0));

peut être écrit comme ceci:

int totalEven = myInts.Where(i => i % 2 == 0).Sum();

Vous pouvez voir comment le deuxième exemple est plus facile à lire. Vous pouvez également voir comment plus de fonctions peuvent être ajoutées avec moins de problèmes d'indentation ou les parens de fermeture Lispy apparaissant à la fin de l'expression.

Beaucoup d'autres réponses indiquent que String.Joinc'est la voie à suivre, car c'est la plus rapide ou la plus simple à lire. Mais si vous prenez mon interprétation de la ` ` façon LINQ super cool '', la réponse est d'utiliser String.Joinmais de l'envelopper dans une méthode d'extension de style LINQ qui vous permettra d'enchaîner vos fonctions d'une manière visuellement agréable. Donc, si vous voulez écrire, il sa.Concatenate(", ")vous suffit de créer quelque chose comme ceci:

public static class EnumerableStringExtensions
{
   public static string Concatenate(this IEnumerable<string> strings, string separator)
   {
      return String.Join(separator, strings);
   }
}

Cela fournira un code aussi performant que l'appel direct (au moins en termes de complexité de l'algorithme) et dans certains cas, rendra le code plus lisible (selon le contexte), surtout si un autre code du bloc utilise le style de fonction chaîné .

tpower
la source
1
Le nombre de fautes de frappe dans ce fil est fou: seperator => separator, Concatinate => Concatenate
SilverSideDown
1
FYI: fusionné de stackoverflow.com/questions/122670/…
Shog9
5

Il existe différentes réponses alternatives à cette question précédente - qui visait certes un tableau entier comme source, mais a reçu des réponses généralisées.

Jon Skeet
la source
5

Ici, il utilise LINQ pur comme une seule expression:

static string StringJoin(string sep, IEnumerable<string> strings) {
  return strings
    .Skip(1)
    .Aggregate(
       new StringBuilder().Append(strings.FirstOrDefault() ?? ""), 
       (sb, x) => sb.Append(sep).Append(x));
}

Et c'est sacrément rapide!

cdiggins
la source
3

Je vais tricher un peu et lancer une nouvelle réponse à cela qui semble résumer le meilleur de tout ici au lieu de la coller dans un commentaire.

Vous pouvez donc une ligne:

List<string> strings = new List<string>() { "one", "two", "three" };

string concat = strings        
    .Aggregate(new StringBuilder("\a"), 
                    (current, next) => current.Append(", ").Append(next))
    .ToString()
    .Replace("\a, ",string.Empty); 

Modifier: vous souhaiterez d'abord vérifier un énumérable vide ou ajouter un.Replace("\a",string.Empty); à la fin de l'expression. Je suppose que j'aurais pu essayer d'être un peu trop intelligent.

La réponse de @ a.friend pourrait être légèrement plus performante, je ne sais pas ce que remplace Replace sous le capot par rapport à Remove. La seule autre mise en garde si une raison pour laquelle vous vouliez concaténer des chaînes qui se terminaient par \ a, vous perdriez vos séparateurs ... Je trouve cela peu probable. Si tel est le cas, vous avez le choix entre d' autres personnages fantaisistes .

Chris Marisic
la source
2

Vous pouvez combiner LINQ et string.join()assez efficacement. Ici, je supprime un élément d'une chaîne. Il existe de meilleures façons de le faire aussi, mais voici:

filterset = String.Join(",",
                        filterset.Split(',')
                                 .Where(f => mycomplicatedMatch(f,paramToMatch))
                       );
Andiih
la source
1
FYI: fusionné de stackoverflow.com/questions/122670/…
Shog9
1

Beaucoup de choix ici. Vous pouvez utiliser LINQ et un StringBuilder pour obtenir les performances aussi comme ceci:

StringBuilder builder = new StringBuilder();
List<string> MyList = new List<string>() {"one","two","three"};

MyList.ForEach(w => builder.Append(builder.Length > 0 ? ", " + w : w));
return builder.ToString();
Kelly
la source
Il serait plus rapide de ne pas vérifier le builder.Length > 0dans le ForEach et en supprimant la première virgule après le ForEach
Binoj Antony
1

J'ai fait ce qui suit rapidement et sale lors de l'analyse d'un fichier journal IIS à l'aide de linq, cela a fonctionné assez bien @ 1 million de lignes (15 secondes), bien que j'ai obtenu une erreur de mémoire insuffisante lors de l'essai de 2 millions de lignes.

    static void Main(string[] args)
    {

        Debug.WriteLine(DateTime.Now.ToString() + " entering main");

        // USED THIS DOS COMMAND TO GET ALL THE DAILY FILES INTO A SINGLE FILE: copy *.log target.log 
        string[] lines = File.ReadAllLines(@"C:\Log File Analysis\12-8 E5.log");

        Debug.WriteLine(lines.Count().ToString());

        string[] a = lines.Where(x => !x.StartsWith("#Software:") &&
                                      !x.StartsWith("#Version:") &&
                                      !x.StartsWith("#Date:") &&
                                      !x.StartsWith("#Fields:") &&
                                      !x.Contains("_vti_") &&
                                      !x.Contains("/c$") &&
                                      !x.Contains("/favicon.ico") &&
                                      !x.Contains("/ - 80")
                                 ).ToArray();

        Debug.WriteLine(a.Count().ToString());

        string[] b = a
                    .Select(l => l.Split(' '))
                    .Select(words => string.Join(",", words))
                    .ToArray()
                    ;

        System.IO.File.WriteAllLines(@"C:\Log File Analysis\12-8 E5.csv", b);

        Debug.WriteLine(DateTime.Now.ToString() + " leaving main");

    }

La vraie raison pour laquelle j'ai utilisé linq était pour un Distinct () dont j'avais besoin auparavant:

string[] b = a
    .Select(l => l.Split(' '))
    .Where(l => l.Length > 11)
    .Select(words => string.Format("{0},{1}",
        words[6].ToUpper(), // virtual dir / service
        words[10]) // client ip
    ).Distinct().ToArray()
    ;
Andy S.
la source
1
FYI: fusionné de stackoverflow.com/questions/122670/…
Shog9
0

J'ai blogué à ce sujet il y a quelque temps, ce que j'ai fait semble être exactement ce que vous recherchez:

http://ondevelopment.blogspot.com/2009/02/string-concatenation-made-easy.html

Dans le billet de blog, décrivez comment implémenter des méthodes d'extension qui fonctionnent sur IEnumerable et sont nommées Concatenate, cela vous permettra d'écrire des choses comme:

var sequence = new string[] { "foo", "bar" };
string result = sequence.Concatenate();

Ou des choses plus élaborées comme:

var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");
Patrik Hägne
la source
1
FYI: fusionné de stackoverflow.com/questions/122670/…
Shog9
Pouvez-vous concaténer du code ici afin que la réponse soit plus facile à comprendre?
Giulio Caccin