LINQ список к формату предложения (вставьте запятые & "и") - PullRequest
6 голосов
/ 29 сентября 2010

У меня есть запрос linq, который делает что-то простое:

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

Затем я хочу функцию или лямбда-строку linq, или что-то, что выводит имена в формате предложения, используя запятые и "ands".

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

до

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

Я в порядке с жестким кодированием части ID + ":" + Name, но это может быть ToString () в зависимости от типа результата запроса linq.Мне просто интересно, есть ли удобный способ сделать это с помощью linq или String.Format ().

Ответы [ 17 ]

6 голосов
/ 29 сентября 2010

Почему Линк?

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 ");
}

Действительно, все, что Linq позволит вам сделать, - это скрыть петлю.

Также убедитесь, что вы хотите или не хотите " Oxford Comma "; этот алгоритм не будет вставлять единицу, но будет небольшое изменение (добавьте запятую и пробел после каждого элемента, кроме последнего, а также добавьте "и" после следующего за последним).

6 голосов
/ 29 сентября 2010
public string ToPrettyCommas<T>(
  List<T> source,
  Func<T, string> stringSelector
)
{
  int count = source.Count;

  Func<int, string> prefixSelector = x => 
    x == 0 ? "" :
    x == count - 1 ? " and " :
    ", ";

  StringBuilder sb = new StringBuilder();

  for(int i = 0; i < count; i++)
  {
    sb.Append(prefixSelector(i));
    sb.Append(stringSelector(source[i]));
  }

  string result = sb.ToString();
  return result;
}

Вызывается с:

string result = ToPrettyCommas(people, p => p.ID.ToString() + ":" + p.Name);
3 голосов
/ 29 сентября 2010

Просто для удовольствия, вот что действительно использует функционал LINQ - без цикла и без StringBuilder.Конечно, это довольно медленно.

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);
1 голос
/ 29 сентября 2010

Используя операцию выбора, которая дает вам индекс, это можно записать как метод расширения ONE LINE:

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" : ""))));
}

например,

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)));
1 голос
/ 29 сентября 2010

Как и все остальные, это не лучше, чем использование строителя строк, но вы можете пойти (игнорируя идентификатор, вы можете добавить его):

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))}));

Этот подход в значительной степени нарушает способность Skip и Take принимать отрицательные числа и готовность String.Join принимать один параметр, поэтому он работает для одной, двух или более строк.

0 голосов
/ 03 октября 2010

Вот один из них, использующий слегка измененную версию моего ответа на Эрика Липперта «Вызов» , который, на мой взгляд, наиболее лаконичен и легко понятен (если вы знакомы с 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);
0 голосов
/ 30 сентября 2010

Вот метод, который не использует LINQ, но, вероятно, настолько эффективен, насколько вы можете его получить:

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 "));

Поскольку он никогда не создает список, не просматривает элемент дважды или не добавляет дополнительные элементы кStringBuilder, я не думаю, что вы можете стать более эффективным.Он также работает для 0, 1 и 2 элементов в списке (как и для других, очевидно).

0 голосов
/ 30 сентября 2010

Я уточнил свой предыдущий ответ и считаю, что это самое элегантное решение.
Однако он будет работать только с ссылочными типами, которые не повторяются в коллекции (иначе нам пришлось бы использовать другие средства для определения, является ли элемент первым / последним).

Наслаждайтесь!

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));
0 голосов
/ 29 сентября 2010

Как насчет этого?

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();
0 голосов
/ 29 сентября 2010

Есть способы оптимизировать это, так как это не очень эффективно, но что-то вроде этого может работать:

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;
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...