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,
Just for fun, here’s something that really uses functional LINQ — no loop and no StringBuilder
. Of course, it’s pretty slow.
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 = (string) 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);
Y'all are making it too complicated:
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 = string.Join(", ", list.Take(list.Count - 2).Concat(new[]{str}));
Here's a method that doesn't use LINQ, but is probably as efficient as you can get:
public static string Join<T>(this IEnumerable<T> list,
string joiner,
string lastJoiner = null)
{
StringBuilder sb = new StringBuilder();
string sep = null, lastItem = null;
foreach (T item in list)
{
if (lastItem != null)
{
sb.Append(sep);
sb.Append(lastItem);
sep = joiner;
}
lastItem = item.ToString();
}
if (lastItem != null)
{
if (sep != null)
sb.Append(lastJoiner ?? joiner);
sb.Append(lastItem);
}
return sb.ToString();
}
Console.WriteLine(people.Select(x => x.ID + ":" + x.Name).Join(", ", " and "));
Since it never creates a list, looks at an element twice, or appends extra stuff to the StringBuilder, I don't think you can get more efficient. It also works for 0, 1, and 2 elements in the list (as well as more, obviously).
This is not pretty but will do the job using LINQ
string s = string.Join(",", k.TakeWhile(X => X != k.Last()).Select(X => X.Id + ":" + X.Name).ToArray()).TrimEnd(",".ToCharArray()) + " And " + k.Last().Id + ":" + k.Last().Name;
Here's one using a slightly modified version of my answer to Eric Lippert's Challenge which is IMHO the most concise with easy to follow logic (if you're familiar with LINQ).
static string CommaQuibblingMod<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); //removed braces
}
//usage
var items = k.Select(item => String.Format("{0}:{1}", item.ID, item.Name));
string formatted = CommaQuibblingMod(items);
static public void Linq1()
{
var k = new[] { new[] { "1", "John" }, new[] { "2", "Mark" }, new[] { "3", "George" } };
Func<string[], string> showPerson = p => p[0] + ": " + p[1];
var res = k.Skip(1).Aggregate(new StringBuilder(showPerson(k.First())),
(acc, next) => acc.Append(next == k.Last() ? " and " : ", ").Append(showPerson(next)));
Console.WriteLine(res);
}
could be optimized by moving k.Last() computation to before the loop