I have a linq query that does something simple like:
var k = people.Select(x=>new{x.ID, x.Name});
I then want a function or linq lambda,
I have refined my previous answer and I believe this is the most elegant solution yet.
However it would only work on reference types that don't repeat in the collection (or else we'd have to use different means for finding out if item is first/last).
Enjoy!
var firstGuy = guys.First();
var lastGuy = guys.Last();
var getSeparator = (Func<Guy, string>)
(guy => {
if (guy == firstGuy) return "";
if (guy == lastGuy) return " and ";
return ", ";
});
var formatGuy = (Func<Guy, string>)
(g => string.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));
Why Linq?
StringBuilder sb = new StringBuilder();
for(int i=0;i<k.Count();i++)
{
sb.Append(String.Format("{0}:{1}", k[i].ID, k[i].Name);
if(i + 2 < k.Count())
sb.Append(", ");
else if(i + 1 < k.Count())
sb.Append(" and ");
}
Really, all Linq will let you do is hide the loop.
Also, make sure you do or do not want the "Oxford Comma"; this algorithm will not insert one, but a small change will (append the comma and space after every element except the last, and also append "and " after the next-to-last).
Improving(hopefully) on KeithS's answer:
string nextBit = "";
var sb = new StringBuilder();
foreach(Person person in list)
{
sb.Append(nextBit);
sb.Append(", ");
nextBit = String.Format("{0}:{1}", person.ID, person.Name);
}
sb.Remove(sb.Length - 3, 2);
sb.Append(" and ");
sb.Append(nextBit);
There are ways to optimize this since it isn't very efficient, but something like this may work:
var k = people.Select(x => new {x.ID, x.Name}).ToList();
var last = k.Last();
k.Aggregate(new StringBuilder(), (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;
});
How about this?
var k = people.Select(x=>new{x.ID, x.Name});
var stringified = people
.Select(x => string.Format("{0} : {1}", x.ID, x.Name))
.ToList();
return string.Join(", ", stringified.Take(stringified.Count-1).ToArray())
+ " and " + stringified.Last();
Much like the rest, this isn't better than using a string builder, but you can go (ignoring the ID, you can add it in):
IEnumerable<string> names = new[] { "Tom", "Dick", "Harry", "Abe", "Bill" };
int count = names.Count();
string s = String.Join(", ", names.Take(count - 2)
.Concat(new [] {String.Join(" and ", names.Skip(count - 2))}));
This approach pretty much abuses Skip
and Take
's ability to take negative numbers, and String.Join
's willingness to take a single parameter, so it works for one, two or more strings.