在C#中用“逗号和逗号”IEnumerable的更好方法

时间:2010-11-21 19:31:29

标签: c# linq string join

  

可能重复:
  LINQ list to sentence format (insert commas & “and”)

想象一下这些输入和结果:

[] -> ""

["Hello World!"] -> "Hello World!"

["Apples", "bananas"] -> "Apples, and bananas" (put your grammar books away)

["Lions", "Tigers", "Bears"] -> "Lions, Tigers, and Bears" (oh my!)

现在,假设输入都是IEnumerable<string>。有什么好处(哪里好可能包含“小而整齐”,“易于理解”,“使用LINQ的全部功能”,或其他只要有理由)在C#中编写一个函数来执行此操作?我真的想避免“强制循环”方法。

我目前的方法如下:

string Commaize (IEnumerable<string> list) {
    if (list.Count() > 1) {
        list = list.Take(list.Count() - 2).Concat(
            new[] { list.Reverse().Take(2).Reverse()
                        .Aggregate((a, b) => a + " and " + b) });
    }
    return String.Join(", ", list.ToArray());
}

但它感觉不是很“好”。它适用于.NET3.5,因此这里需要ToArray()位。如果list为空,则结果为UB。

5 个答案:

答案 0 :(得分:7)

与其他答案(CodeInChaos发布的答案除外)不同,此实现仅枚举输入序列一次。如果枚举它的成本很高(例如数据库查询,Web服务调用......)

,这可能很重要
string Commaize (IEnumerable<string> list)
{
    string previous = null;
    StringBuilder sb = new StringBuilder();
    foreach(string s in list)
    {
        if (previous != null)
            sb.AppendFormat("{0}, ", previous);
        previous = s;
    }
    if (previous != null)
    {
        if (sb.Length > 0)
            sb.AppendFormat("and {0}", previous);
        else
            sb.Append(previous);
    }
    return sb.ToString();
}

答案 1 :(得分:1)

string Commaize (IEnumerable<string> sequence)
{
    IList<string> list=sequence as IList<string>;
    if(list==null)
      list=sequence.ToList();
    if(list.Count==0)
      return "";
    else if(list.Count==1)
      return list.First();
    else
      return String.Join(", ", list.Take(list.Count-1).ToArray()) + " and " + list.Last();
}

这样的开销是分配一些额外的数组(一个ToList()和一个ToArray()调用,它们可能都使用指数增长数组的分配,因此分配的数组的数量大于2)。 / p>

答案 2 :(得分:1)

从链接的问题,扩展方法:

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

编辑注意:我认为我们可以放心地假设英语句子不会在重复使用Count()和/或未来的编译器可能会导致任何性能问题为我们优化这一点。但是,是的,如果你想为可以有效实现Count的可枚举集合优化它,你可以将Count()移出语句。

答案 3 :(得分:1)

string Commaize (IEnumerable<string> list) {
    var last = list.LastOrDefault();
    return (last != null) ?
        list.Aggregate((acc,x) => acc + ", " + (x == last ? "and " : "") + x) :
        string.Empty;
}

对于10,000个字符串的列表,它在0.5秒内运行(与在大约.005秒内运行的Thomas'相比)。不是最快的,但我确实喜欢可读性。

修改

string Commaize (IEnumerable<string> list) {
    var enumer = list.GetEnumerator();
    if (enumer.MoveNext()) {
        var c = enumer.Current;
        return (enumer.MoveNext()) ?
        list.Aggregate((acc,x) => acc + ", " + (!enumer.MoveNext() ? "and " : "") + x) :
            c;
    }
    return string.Empty;
}

这个版本没有第一个版本的等式问题,但是以可读性为代价,这实际上是第一个函数唯一的用途。

答案 4 :(得分:0)

这是我在implementation中找到的Eric Lippert's challenge(使用.NET 4.0):

static string CommaQuibbling<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) + "}";
}

如果需要,请添加牛津逗号,如果不需要,请删除额外的括号。