Liste LINQ au format de phrase (insérer des virgules & «et»)

J’ai une requête linq qui fait quelque chose de simple comme:

var k = people.Select(x=>new{x.ID, x.Name}); 

Je veux ensuite une fonction ou linq lambda, ou quelque chose qui affichera les noms au format phrase en utilisant des virgules et “ands”.

 {1, John} {2, Mark} {3, George} 

à

 "1:John, 2:Mark and 3:George" 

Je suis bien avec coder en dur l’ ID + ":" + Name partie, mais il pourrait être une ToSsortingng () en fonction du type du résultat de la requête linq. Je me demande simplement s’il existe un moyen intéressant de le faire avec linq ou Ssortingng.Format ().

 public ssortingng ToPrettyCommas( List source, Func ssortingngSelector ) { int count = source.Count; Func prefixSelector = x => x == 0 ? "" : x == count - 1 ? " and " : ", "; SsortingngBuilder sb = new SsortingngBuilder(); for(int i = 0; i < count; i++) { sb.Append(prefixSelector(i)); sb.Append(stringSelector(source[i])); } string result = sb.ToString(); return result; } 

Appelé avec:

 ssortingng result = ToPrettyCommas(people, p => p.ID.ToSsortingng() + ":" + p.Name); 

Pourquoi Linq?

 SsortingngBuilder sb = new SsortingngBuilder(); for(int i=0;i 

Vraiment, tout ce que Linq vous laissera faire est de cacher la boucle.

Assurez-vous également que vous ne voulez pas utiliser la " Comma " Oxford ; cet algorithme n'en insérera pas un, mais un petit changement le fera (appenda la virgule et l'espace après chaque élément sauf le dernier, et appenda aussi "et" après l'avant-dernier).

Juste pour le plaisir, voici quelque chose qui utilise vraiment LINQ fonctionnel – pas de boucle ni de SsortingngBuilder . Bien sûr, c’est assez lent.

 var list = new[] { new { ID = 1, Name = "John" }, new { ID = 2, Name = "Mark" }, new { ID = 3, Name = "George" } }; var resultAggr = list .Select(item => item.ID + ":" + item.Name) .Aggregate(new { Sofar = "", Next = (ssortingng) null }, (agg, next) => new { Sofar = agg.Next == null ? "" : agg.Sofar == "" ? agg.Next : agg.Sofar + ", " + agg.Next, Next = next }); var result = resultAggr.Sofar == "" ? resultAggr.Next : resultAggr.Sofar + " and " + resultAggr.Next; // Prints 1:John, 2:Mark and 3:George Console.WriteLine(result); 

Comme le rest, ce n’est pas mieux que d’utiliser un constructeur de chaînes, mais vous pouvez y aller (en ignorant l’ID, vous pouvez l’append):

 IEnumerable names = new[] { "Tom", "Dick", "Harry", "Abe", "Bill" }; int count = names.Count(); ssortingng s = Ssortingng.Join(", ", names.Take(count - 2) .Concat(new [] {Ssortingng.Join(" and ", names.Skip(count - 2))})); 

Cette approche abuse assez de la capacité de Skip and Take de prendre des nombres négatifs et de la volonté de Ssortingng.Join de prendre un seul paramètre, de sorte que cela fonctionne pour une, deux ou plusieurs chaînes.

A l’aide de l’opération Select qui vous donne un index, vous pouvez l’écrire en tant que méthode d’extension ONE LINE:

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

par exemple

 var list = new[] { new { ID = 1, Name = "John" }, new { ID = 2, Name = "Mark" }, new { ID = 3, Name = "George" } }.ToList(); Console.WriteLine(list.ToAndList(x => (x.ID + ": " + x.Name))); 

Améliorer (espérons) la réponse de KeithS:

 ssortingng nextBit = ""; var sb = new SsortingngBuilder(); foreach(Person person in list) { sb.Append(nextBit); sb.Append(", "); nextBit = Ssortingng.Format("{0}:{1}", person.ID, person.Name); } sb.Remove(sb.Length - 3, 2); sb.Append(" and "); sb.Append(nextBit); 

Ce n’est pas joli mais fera le travail en utilisant LINQ

 ssortingng s = ssortingng.Join(",", k.TakeWhile(X => X != k.Last()).Select(X => X.Id + ":" + X.Name).ToArray()).TrimEnd(",".ToCharArray()) + " And " + k.Last().Id + ":" + k.Last().Name; 

Vous rendez la chose trop compliquée:

 var list = k.Select(x => x.ID + ":" + x.Name).ToList(); var str = list.LastOrDefault(); str = (list.Count >= 2 ? list[list.Count - 2] + " and " : null) + str; str = ssortingng.Join(", ", list.Take(list.Count - 2).Concat(new[]{str})); 

Que dis-tu de ça?

 var k = people.Select(x=>new{x.ID, x.Name}); var ssortingngified = people .Select(x => ssortingng.Format("{0} : {1}", x.ID, x.Name)) .ToList(); return ssortingng.Join(", ", ssortingngified.Take(ssortingngified.Count-1).ToArray()) + " and " + ssortingngified.Last(); 

J’ai affiné ma réponse précédente et je crois que c’est la solution la plus élégante à ce jour.
Cependant, cela ne fonctionnerait que sur des types de référence qui ne se répètent pas dans la collection (ou nous devrions utiliser différents moyens pour déterminer si l’élément est premier / dernier).

Prendre plaisir!

 var firstGuy = guys.First(); var lastGuy = guys.Last(); var getSeparator = (Func) (guy => { if (guy == firstGuy) return ""; if (guy == lastGuy) return " and "; return ", "; }); var formatGuy = (Func) (g => ssortingng.Format("{0}:{1}", g.Id, g.Name)); // 1:John, 2:Mark and 3:George var summary = guys.Aggregate("", (sum, guy) => sum + getSeparator(guy) + formatGuy(guy)); 

Cela peut être la façon dont vous pouvez atteindre votre objective

 var list = new[] { new { ID = 1, Name = "John" }, new { ID = 2, Name = "Mark" }, new { ID = 3, Name = "George" } }.ToList(); int i = 0; ssortingng str = ssortingng.Empty; var k = list.Select(x => x.ID.ToSsortingng() + ":" + x.Name + ", ").ToList(); k.ForEach(a => { if (i < k.Count() - 1) { str = str + a; } else { str = str.Substring(0, str.Length -2) + " and " + a.Replace("," , ""); } i++; }); 

Voici une méthode qui n’utilise pas LINQ, mais qui est probablement aussi efficace que possible:

 public static ssortingng Join(this IEnumerable list, ssortingng joiner, ssortingng lastJoiner = null) { SsortingngBuilder sb = new SsortingngBuilder(); ssortingng sep = null, lastItem = null; foreach (T item in list) { if (lastItem != null) { sb.Append(sep); sb.Append(lastItem); sep = joiner; } lastItem = item.ToSsortingng(); } if (lastItem != null) { if (sep != null) sb.Append(lastJoiner ?? joiner); sb.Append(lastItem); } return sb.ToSsortingng(); } Console.WriteLine(people.Select(x => x.ID + ":" + x.Name).Join(", ", " and ")); 

Puisqu’il ne crée jamais de liste, ne regarde pas un élément deux fois, ou ajoute des éléments supplémentaires à SsortingngBuilder, je ne pense pas que vous puissiez être plus efficace. Cela fonctionne également pour les éléments 0, 1 et 2 de la liste (ainsi que d’autres, évidemment).

Approche SsortingngBuilder

Voici un Aggregate avec un SsortingngBuilder . Certaines déterminations de position sont effectuées pour nettoyer la chaîne et insérer le “et”, mais tout se fait au niveau de SsortingngBuilder .

 var people = new[] { new { Id = 1, Name = "John" }, new { Id = 2, Name = "Mark" }, new { Id = 3, Name = "George" } }; var sb = people.Aggregate(new SsortingngBuilder(), (s, p) => s.AppendFormat("{0}:{1}, ", p.Id, p.Name)); sb.Remove(sb.Length - 2, 2); // remove the trailing comma and space var last = people.Last(); // index to last comma (-2 accounts for ":" and space prior to last name) int indexComma = sb.Length - last.Id.ToSsortingng().Length - last.Name.Length - 2; sb.Remove(indexComma - 1, 1); // remove last comma between last 2 names sb.Insert(indexComma, "and "); // 1:John, 2:Mark and 3:George Console.WriteLine(sb.ToSsortingng()); 

Une approche Ssortingng.Join aurait pu être utilisée à la place, mais l’insertion “et” et le retrait de la virgule généreraient environ 2 nouvelles chaînes.


Approche regex

Voici une autre approche utilisant regex qui est tout à fait compréhensible (rien de trop cryptique).

 var people = new[] { new { Id = 1, Name = "John" }, new { Id = 2, Name = "Mark" }, new { Id = 3, Name = "George" } }; var joined = Ssortingng.Join(", ", people.Select(p => p.Id + ":" + p.Name).ToArray()); Regex rx = new Regex(", ", RegexOptions.RightToLeft); ssortingng result = rx.Replace(joined, " and ", 1); // make 1 replacement only Console.WriteLine(result); 

Le motif est simplement ", " . La magie réside dans les RegexOptions.RightToLeft qui permettent d’ RegexOptions.RightToLeft la correspondance à partir de la droite et, par conséquent, de remplacer le remplacement à la dernière occurrence de la virgule. Il n’y a pas de méthode Regex statique qui accepte le nombre de remplacements avec RegexOptions , d’où l’utilisation d’instance.

En voici une qui utilise une version légèrement modifiée de ma réponse au Challenge d’Eric Lippert, qui est le plus concis à mon humble avis, avec une logique facile à suivre (si vous connaissez LINQ).

 static ssortingng CommaQuibblingMod(IEnumerable 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 ? Ssortingng.Join(", ", g) : Ssortingng.Join(" and ", g)); return Ssortingng.Join(", ", quibbled); //removed braces } //usage var items = k.Select(item => Ssortingng.Format("{0}:{1}", item.ID, item.Name)); ssortingng formatted = CommaQuibblingMod(items); 
 static public void Linq1() { var k = new[] { new[] { "1", "John" }, new[] { "2", "Mark" }, new[] { "3", "George" } }; Func showPerson = p => p[0] + ": " + p[1]; var res = k.Skip(1).Aggregate(new SsortingngBuilder(showPerson(k.First())), (acc, next) => acc.Append(next == k.Last() ? " and " : ", ").Append(showPerson(next))); Console.WriteLine(res); } 

pourrait être optimisé en déplaçant le calcul de k.Last () avant la boucle

  public static ssortingng ToListingCommaFormat(this List ssortingngList) { switch(ssortingngList.Count) { case 0: return ""; case 1: return ssortingngList[0]; case 2: return ssortingngList[0] + " and " + ssortingngList[1]; default: return Ssortingng.Join(", ", ssortingngList.GetRange(0, ssortingngList.Count-1)) + ", and " + ssortingngList[ssortingngList.Count - 1]; } } 

Cette méthode est plus rapide que la méthode de jointure «efficace» publiée par Gabe. Pour un et deux éléments, il est beaucoup plus rapide, et pour 5-6 chaînes, il est environ 10% plus rapide. Il n’y a pas de dépendance à LINQ. Ssortingng.Join est plus rapide que SsortingngBuilder pour les petits tableaux, qui sont typiques pour du texte lisible par l’homme. En grammaire, cela s’appelle des listes de virgules et la dernière virgule doit toujours être incluse pour éviter toute ambiguïté. Voici le code résultant:

people.Select(x=> x.ID.ToSsortingng() + ":" + x.Name).ToList().ToListingCommaFormat();

Il existe des moyens d’optimiser cela car ce n’est pas très efficace, mais cela peut fonctionner:

 var k = people.Select(x => new {x.ID, x.Name}).ToList(); var last = k.Last(); k.Aggregate(new SsortingngBuilder(), (sentence, item) => { if (sentence.Length > 0) { if (item == last) sentence.Append(" and "); else sentence.Append(", "); } sentence.Append(item.ID).Append(":").Append(item.Name); return sentence; });