【问题标题】:A better way to "comma and and-ize" an IEnumerable in C# [duplicate]在 C# 中“逗号和化” IEnumerable 的更好方法 [重复]
【发布时间】:2010-11-21 19:31:29
【问题描述】:

可能重复:
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>。用 C# 编写一个函数来做到这一点是什么好(好可能包括“小而整洁”、“易于理解”、“使用 LINQ 的全部功能”或其他,只要它是合理的)?我真的很想避免“命令式循环”方法。

我目前的做法是这样的:

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 为 null,则结果为 UB。

【问题讨论】:

  • 不是一个 exact 重复...这个使用牛津逗号,另一个问题没有。它实际上可以改变实现(我知道它改变了我的一点)。另外,请参阅Jeff Atwood's recent blog post,了解复制实际上是一件好事。

标签: c# linq string join


【解决方案1】:

与其他答案不同(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();
}

【讨论】:

  • 你的内存占用更少,因为你没有ToList/ToArray 调用。所以它可能会快一点。
  • 你遍历列表两次而不是一次:sb.ToString();遍历它
  • @Saeed: sb 是一个 StringBuilder,它不遍历任何列表...
  • 那么如何将char流转换为字符串呢?
  • @Saeed,它只返回当前在 StringBuilder 缓冲区中的字符串。
【解决方案2】:
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() 调用,它们可能都使用指数增长数组的分配,因此分配的数组数量大于两个)。

【讨论】:

  • +1。我真的很喜欢 Take 和 Last 的这种用法(我忘记了 Last)以及“加入”的分离。但是,我不喜欢顶部的“转换检查”。
【解决方案3】:
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 秒内运行(相比之下,Thomas 的运行时间约为 0.005 秒)。不是最快的,但我确实喜欢可读性。

编辑:

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

这个版本没有第一个版本的相等问题,.....但是以可读性为代价,这确实是第一个函数唯一要做的事情。

【讨论】:

  • 我对此投了赞成票,尽管如果x == last,当“x 不是最后一个”时,这将不起作用。使用 object-identity (object)x == (object)last 可以使我的弹性稍微提高一些,但这仍然存在不能在输入中复制特定对象的限制。
【解决方案4】:

这是我在Eric Lippert's challenge 中找到的implementation(使用.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) + "}";
}

如果需要,请添加牛津逗号,如果不需要,请删除多余的大括号。

【讨论】:

    【解决方案5】:

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

    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() 移出语句。

    【讨论】:

    • 这是 O(n^2),但对于小型列表可能仍然可以接受。
    • 您可能希望将list.Count() 移出循环。
    • 实际上只有当输入序列没有实现 ICollection 时它才是 O(n²)...但是无论如何,当您不知道输入实际上是什么时,您不应该调用 Count()反复在上面
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-16
    • 2017-11-19
    • 1970-01-01
    • 2018-02-20
    • 2015-04-08
    • 1970-01-01
    相关资源
    最近更新 更多