LINQ list to sentence format (insert commas & “and

2020-03-20 12:36发布

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, or something that will output the names in sentence format using commas and "ands".

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

to

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

I'm fine with hardcoding the ID + ":" + Name part, but it could be a ToString() depending on the type of the linq query result. I'm just wondering if there is a neat way to do this with linq or String.Format().

标签: c# linq string
17条回答
三岁会撩人
2楼-- · 2020-03-20 12:50

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.

查看更多
祖国的老花朵
3楼-- · 2020-03-20 12:51
    public static string ToListingCommaFormat(this List<string> stringList)
    {
        switch(stringList.Count)
        {
            case 0:
                return "";
            case 1:
                return stringList[0];
            case 2:
                return stringList[0] + " and " + stringList[1];
            default:
                return String.Join(", ", stringList.GetRange(0, stringList.Count-1)) 
                    + ", and " + stringList[stringList.Count - 1];
        }
    }

This is the method is faster than the 'efficient' Join method posted by Gabe. For one and two items, it is many times faster, and for 5-6 strings, it is about 10% faster. There is no dependency on LINQ. String.Join is faster than StringBuilder for small arrays, which are typical for human-readable text. In grammar, these are called listing commas, and the last comma should always be included to avoid ambiguity. Here is the resulting code:

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

查看更多
The star\"
4楼-- · 2020-03-20 12:53

Using the Select operation that gives you an index, this can be written as a ONE LINE extension method:

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

e.g.

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)));
查看更多
可以哭但决不认输i
5楼-- · 2020-03-20 13:00

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;
查看更多
▲ chillily
6楼-- · 2020-03-20 13:01
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

查看更多
贼婆χ
7楼-- · 2020-03-20 13:02

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).

查看更多
登录 后发表回答