2010-11-21 21 views
0

Possible en double:
LINQ list to sentence format (insert commas & “and”)Une meilleure façon de "virgule et et iser" un IEnumerable en C#

Imaginer ces entrées et résultats:

[] -> "" 

["Hello World!"] -> "Hello World!" 

["Apples", "bananas"] -> "Apples, and bananas" (put your grammar books away) 

["Lions", "Tigers", "Bears"] -> "Lions, Tigers, and Bears" (oh my!) 

maintenant , imaginez que les entrées sont toutes IEnumerable<string>. Qu'est-ce qu'un bien (où bon peut englober "petit et bien rangé", "facile à comprendre", "utilise la pleine capacité de LINQ", ou autre tant que cela est justifié) pour écrire une fonction en C# pour cela? Je voudrais vraiment éviter les approches "en boucle impérative".

Mon approche actuelle ressemble à:

string Commaize (IEnumerable<string> list) { 
    if (list.Count() > 1) { 
     list = list.Take(list.Count() - 2).Concat(
      new[] { list.Reverse().Take(2).Reverse() 
         .Aggregate((a, b) => a + " and " + b) }); 
    } 
    return String.Join(", ", list.ToArray()); 
} 

Mais ça ne se sent pas très « bon ». C'est pour .NET3.5 donc le bit ToArray() est requis ici. Si list est nul, le résultat est UB.

+1

Pas un doublon * exact * ... celui-ci utilise la virgule d'Oxford, l'autre question ne le fait pas. Il peut réellement changer la mise en œuvre (je sais que ça change un peu le mien). Aussi, voir [le blog récent de Jeff Atwood] (http://blog.stackoverflow.com/2010/11/dr-strangedupe-or-how-i-learned-to-stop-worrying-and-love-duplication/) à propos de la façon dont la duplication est en fait une bonne chose. –

Répondre

1
string Commaize (IEnumerable<string> sequence) 
{ 
    IList<string> list=sequence as IList<string>; 
    if(list==null) 
     list=sequence.ToList(); 
    if(list.Count==0) 
     return ""; 
    else if(list.Count==1) 
     return list.First(); 
    else 
     return String.Join(", ", list.Take(list.Count-1).ToArray()) + " and " + list.Last(); 
} 

Les frais généraux de c'est l'attribution de quelques tableaux supplémentaires (un ToList() et un appel, ce qui a probablement aussi bien utiliser l'allocation ToArray() des tableaux en croissance exponentielle, de sorte que le nombre de tableaux attribués est plus grand que deux).

+0

+1. J'aime beaucoup cette utilisation de Take and Last (j'avais oublié Last) et la séparation de ce qui est «joint». Cependant, je n'aime pas les "chèques de conversion" en haut. –

0

Voici mon implementation (en utilisant .NET 4.0) de celui-ci trouve dans Eric Lippert's challenge:

static string CommaQuibbling<T>(IEnumerable<T> items) 
{ 
    int count = items.Count(); 
    var quibbled = items.Select((Item, index) => new { Item, Group = (count - index - 2) > 0}) 
         .GroupBy(item => item.Group, item => item.Item) 
         .Select(g => g.Key 
          ? String.Join(", ", g) 
          : String.Join(" and ", g)); 
    return "{" + String.Join(", ", quibbled) + "}"; 
} 

Ajouter la virgule Oxford si vous le voulez et enlever les accolades supplémentaires si vous ne le faites pas. De la question liée

1

, une méthode d'extension:

public static string ToAndList<T>(this IEnumerable<T> list) 
{ 
    return string.Join(" ", list.Select((x, i) => x.ToString() + (i < list.Count() - 2 ? ", " : (i < list.Count() - 1 ? " and" : "")))); 
} 

Edit: Note: Je pense que nous pouvons supposer que les peines anglais ne vont pas causer des problèmes de performance ici avec l'utilisation répétée de Count() et/ou que les futurs compilateurs pourraient être en mesure d'optimiser cela pour nous. Mais oui, si vous voulez l'optimiser pour des collections énumérables qui n'implémentent pas Count efficacement, vous pouvez déplacer Count() hors de l'instruction.

+0

Ceci est O (n^2), mais pourrait toujours être acceptable pour les petites listes. – CodesInChaos

+0

Vous pouvez déplacer 'list.Count()' hors de la boucle. – CodesInChaos

+0

En fait c'est O (n²) seulement si la séquence d'entrée n'implémente pas ICollection ... Mais de toute façon, quand vous ne savez pas ce que l'entrée est réellement, vous ne devriez pas appeler 'Count()' plusieurs fois dessus –

6

Contrairement à d'autres réponses (à l'exception de celle publiée par CodeInChaos), cette implémentation n'énumère qu'une seule fois la séquence d'entrée. Cela peut être important si le coût de l'énumération, il est élevé (par exemple, la requête de DB, appels de service web ...)

string Commaize (IEnumerable<string> list) 
{ 
    string previous = null; 
    StringBuilder sb = new StringBuilder(); 
    foreach(string s in list) 
    { 
     if (previous != null) 
      sb.AppendFormat("{0}, ", previous); 
     previous = s; 
    } 
    if (previous != null) 
    { 
     if (sb.Length > 0) 
      sb.AppendFormat("and {0}", previous); 
     else 
      sb.Append(previous); 
    } 
    return sb.ToString(); 
} 
+0

prend moins de mémoire car vous n'avez pas les appels 'ToList' /' ToArray'. Donc, c'est probablement un peu plus rapide. – CodesInChaos

+0

Vous parcourez la liste deux fois pas une fois: sb.ToString(); traverse –

+0

@Saeed: sb est un StringBuilder, il ne traverse aucune liste ... –

1
string Commaize (IEnumerable<string> list) { 
    var last = list.LastOrDefault(); 
    return (last != null) ? 
     list.Aggregate((acc,x) => acc + ", " + (x == last ? "and " : "") + x) : 
     string.Empty; 
} 

Pour une liste de 10.000 chaînes ce couru en .5 secondes (comparer à Thomas 'qui tourne dans environ 005 secondes). Pas le plus rapide, mais j'aime la lisibilité.

EDIT:

string Commaize (IEnumerable<string> list) { 
    var enumer = list.GetEnumerator(); 
    if (enumer.MoveNext()) { 
     var c = enumer.Current; 
     return (enumer.MoveNext()) ? 
     list.Aggregate((acc,x) => acc + ", " + (!enumer.MoveNext() ? "and " : "") + x) : 
      c; 
    } 
    return string.Empty; 
} 

Cette version n'a pas le problème de l'égalité du premier, ..... mais au prix de la lisibilité, ce qui était vraiment la seule chose que le premier la fonction avait pour elle.

+0

J'ai voté pour cela, bien que cela ne fonctionne pas si 'x == last', quand" x n'est pas le dernier ". Je peux être rendu légèrement plus résilient en utilisant l'identité d'objet '(objet) x == (objet) last', mais cela a toujours la restriction qu'un objet particulier ne peut pas être dupliqué dans l'entrée. –

+0

Ahhh .... bonne prise! – diceguyd30