23

我想让这个挑战引起 stackoverflow 社区的注意。原来的问题和答案都在这里。BTW,如果你以前没有关注过,你应该尝试阅读 Eric 的博客,这是纯粹的智慧。

概括:

编写一个函数,它接受一个非空 IEnumerable 并返回一个具有以下特征的字符串:

  1. 如果序列为空,则结果字符串为“{}”。
  2. 如果序列是单个项目“ABC”,则结果字符串为“{ABC}”。
  3. 如果序列是两个项目序列“ABC”、“DEF”,则结果字符串为“{ABC 和 DEF}”。
  4. 如果序列有两个以上的项目,比如“ABC”、“DEF”、“G”、“H”,那么结果字符串是“{ABC, DEF, G and H}”。(注意:没有牛津逗号!)

正如您所看到的,甚至我们自己的 Jon Skeet(是的,众所周知,他可以同时在两个地方)发布了一个解决方案,但他的(恕我直言)并不是最优雅的,尽管您可能无法击败它表现。

你怎么看?那里有很多不错的选择。我真的很喜欢涉及选择和聚合方法的解决方案之一(来自 Fernando Nicolet)。Linq 非常强大,花一些时间来应对这样的挑战会让你学到很多东西。我稍微扭曲了它,让它更高效更清晰(通过使用 Count 并避免 Reverse):

public static string CommaQuibbling(IEnumerable<string> items)
{
    int last = items.Count() - 1;
    Func<int, string> getSeparator = (i) => i == 0 ? string.Empty : (i == last ? " and " : ", ");
    string answer = string.Empty;

    return "{" + items.Select((s, i) => new { Index = i, Value = s })
                      .Aggregate(answer, (s, a) => s + getSeparator(a.Index) + a.Value) + "}";
}
4

27 回答 27

33

效率低下,但我认为清楚。

public static string CommaQuibbling(IEnumerable<string> items)
{
    List<String> list = new List<String>(items);
    if (list.Count == 0) { return "{}"; }
    if (list.Count == 1) { return "{" + list[0] + "}"; }

    String[] initial = list.GetRange(0, list.Count - 1).ToArray();
    return "{" + String.Join(", ", initial) + " and " + list[list.Count - 1] + "}";
}

如果我在维护代码,我更喜欢这个而不是更聪明的版本。

于 2009-04-25T19:52:20.810 回答
28

这种方法怎么样?纯粹累积 - 没有回溯,并且只迭代一次。对于原始性能,我不确定您是否会在使用 LINQ 等方面做得更好,无论 LINQ 答案可能有多“漂亮”。

using System;
using System.Collections.Generic;
using System.Text;

static class Program
{
    public static string CommaQuibbling(IEnumerable<string> items)
    {
        StringBuilder sb = new StringBuilder('{');
        using (var iter = items.GetEnumerator())
        {
            if (iter.MoveNext())
            { // first item can be appended directly
                sb.Append(iter.Current);
                if (iter.MoveNext())
                { // more than one; only add each
                  // term when we know there is another
                    string lastItem = iter.Current;
                    while (iter.MoveNext())
                    { // middle term; use ", "
                        sb.Append(", ").Append(lastItem);
                        lastItem = iter.Current;
                    }
                    // add the final term; since we are on at least the
                    // second term, always use " and "
                    sb.Append(" and ").Append(lastItem);
                }
            }
        }
        return sb.Append('}').ToString();
    }
    static void Main()
    {
        Console.WriteLine(CommaQuibbling(new string[] { }));
        Console.WriteLine(CommaQuibbling(new string[] { "ABC" }));
        Console.WriteLine(CommaQuibbling(new string[] { "ABC", "DEF" }));
        Console.WriteLine(CommaQuibbling(new string[] {
             "ABC", "DEF", "G", "H" }));
    }
}
于 2009-04-25T08:53:33.797 回答
5

如果我对需要第一个/最后一个信息的流做了很多工作,我会有扩展名:

[Flags]
public enum StreamPosition
{
   First = 1, Last = 2
}

public static IEnumerable<R> MapWithPositions<T, R> (this IEnumerable<T> stream, 
    Func<StreamPosition, T, R> map)
{
    using (var enumerator = stream.GetEnumerator ())
    {
        if (!enumerator.MoveNext ()) yield break ;

        var cur   = enumerator.Current   ;
        var flags = StreamPosition.First ;
        while (true)
        {
            if (!enumerator.MoveNext ()) flags |= StreamPosition.Last ;
            yield return map (flags, cur) ;
            if ((flags & StreamPosition.Last) != 0) yield break ;
            cur   = enumerator.Current ;
            flags = 0 ;
        }
    }
}

然后最简单的(不是最快的,需要一些更方便的扩展方法)解决方案将是:

public static string Quibble (IEnumerable<string> strings)
{
    return "{" + String.Join ("", strings.MapWithPositions ((pos, item) => (
       (pos &  StreamPosition.First) != 0      ? "" : 
        pos == StreamPosition.Last   ? " and " : ", ") + item)) + "}" ;
}
于 2009-04-25T10:04:06.497 回答
3

这里作为 Python 单线


>>> f=lambda s:"{%s}"%", ".join(s)[::-1].replace(',','dna ',1)[::-1]
>>> f([])
'{}'
>>> f(["ABC"])
'{ABC}'
>>> f(["ABC","DEF"])
'{ABC and DEF}'
>>> f(["ABC","DEF","G","H"])
'{ABC, DEF, G and H}'

这个版本可能更容易理解


>>> f=lambda s:"{%s}"%" and ".join(s).replace(' and',',',len(s)-2)
>>> f([])
'{}'
>>> f(["ABC"])
'{ABC}'
>>> f(["ABC","DEF"])
'{ABC and DEF}'
>>> f(["ABC","DEF","G","H"])
'{ABC, DEF, G and H}'
于 2009-10-12T00:44:00.727 回答
2

这是一个简单的 F# 解决方案,它只进行一次前向迭代:

let CommaQuibble items =
    let sb = System.Text.StringBuilder("{")
    // pp is 2 previous, p is previous
    let pp,p = items |> Seq.fold (fun (pp:string option,p) s -> 
        if pp <> None then
            sb.Append(pp.Value).Append(", ") |> ignore
        (p, Some(s))) (None,None)
    if pp <> None then
        sb.Append(pp.Value).Append(" and ") |> ignore
    if p <> None then
        sb.Append(p.Value) |> ignore
    sb.Append("}").ToString()

(编辑:事实证明这与 Skeet 的非常相似。)

测试代码:

let Test l =
    printfn "%s" (CommaQuibble l)

Test []
Test ["ABC"]        
Test ["ABC";"DEF"]        
Test ["ABC";"DEF";"G"]        
Test ["ABC";"DEF";"G";"H"]        
Test ["ABC";null;"G";"H"]        
于 2009-04-25T09:15:23.853 回答
2

免责声明:我以此为借口来玩弄新技术,所以我的解决方案并没有真正满足 Eric 最初对清晰度和可维护性的要求。

朴素的枚举器解决方案 (我承认这个foreach变体是优越的,因为它不需要手动弄乱枚举器。)

public static string NaiveConcatenate(IEnumerable<string> sequence)
{
    StringBuilder sb = new StringBuilder();
    sb.Append('{');

    IEnumerator<string> enumerator = sequence.GetEnumerator();

    if (enumerator.MoveNext())
    {
        string a = enumerator.Current;
        if (!enumerator.MoveNext())
        {
            sb.Append(a);
        }
        else
        {
            string b = enumerator.Current;
            while (enumerator.MoveNext())
            {
                sb.Append(a);
                sb.Append(", ");
                a = b;
                b = enumerator.Current;
            }
            sb.AppendFormat("{0} and {1}", a, b);
        }
    }

    sb.Append('}');
    return sb.ToString();
}

使用 LINQ 的解决方案

public static string ConcatenateWithLinq(IEnumerable<string> sequence)
{
    return (from item in sequence select item)
        .Aggregate(
        new {sb = new StringBuilder("{"), a = (string) null, b = (string) null},
        (s, x) =>
            {
                if (s.a != null)
                {
                    s.sb.Append(s.a);
                    s.sb.Append(", ");
                }
                return new {s.sb, a = s.b, b = x};
            },
        (s) =>
            {
                if (s.b != null)
                    if (s.a != null)
                        s.sb.AppendFormat("{0} and {1}", s.a, s.b);
                    else
                        s.sb.Append(s.b);
                s.sb.Append("}");
                return s.sb.ToString();
            });
}

使用 TPL 的解决方案

该解决方案使用生产者-消费者队列将输入序列提供给处理器,同时在队列中保留至少两个缓冲的元素。一旦生产者到达输入序列的末尾,就可以对最后两个元素进行特殊处理。

事后看来,没有理由让消费者异步操作,这将消除对并发队列的需要,但正如我之前所说,我只是以此为借口来玩新技术:-)

public static string ConcatenateWithTpl(IEnumerable<string> sequence)
{
    var queue = new ConcurrentQueue<string>();
    bool stop = false;

    var consumer = Future.Create(
        () =>
            {
                var sb = new StringBuilder("{");
                while (!stop || queue.Count > 2)
                {
                    string s;
                    if (queue.Count > 2 && queue.TryDequeue(out s))
                        sb.AppendFormat("{0}, ", s);
                }
                return sb;
            });

    // Producer
    foreach (var item in sequence)
        queue.Enqueue(item);

    stop = true;
    StringBuilder result = consumer.Value;

    string a;
    string b;

    if (queue.TryDequeue(out a))
        if (queue.TryDequeue(out b))
            result.AppendFormat("{0} and {1}", a, b);
        else
            result.Append(a);

    result.Append("}");
    return result.ToString();
}

为简洁起见省略了单元测试。

于 2009-04-25T10:10:46.047 回答
2

我是连环逗号的粉丝:我吃,开枪,然后离开。

我一直需要解决这个问题,并且已经用 3 种语言(虽然不是 C#)解决了它。我将通过编写适用于 any 的方法来调整以下解决方案(在Lua中,不将答案括在花括号中):concatIEnumerable

function commafy(t, andword)
  andword = andword or 'and'
  local n = #t -- number of elements in the numeration
  if n == 1 then
    return t[1]
  elseif n == 2 then
    return concat { t[1], ' ', andword, ' ', t[2] }
  else
    local last = t[n]
    t[n] = andword .. ' ' .. t[n]
    local answer = concat(t, ', ')
    t[n] = last
    return answer
  end
end
于 2009-05-02T17:30:25.943 回答
2

这不是很好的可读性,但它可以很好地扩展到数千万个字符串。我正在一个旧的 Pentium 4 工作站上开发,它在大约 350 毫秒内完成了 1,000,000 个平均长度为 8 的字符串。

public static string CreateLippertString(IEnumerable<string> strings)
{
    char[] combinedString;
    char[] commaSeparator = new char[] { ',', ' ' };
    char[] andSeparator = new char[] { ' ', 'A', 'N', 'D', ' ' };

    int totalLength = 2;  //'{' and '}'
    int numEntries = 0;
    int currentEntry = 0;
    int currentPosition = 0;
    int secondToLast;
    int last;
    int commaLength= commaSeparator.Length;
    int andLength = andSeparator.Length;
    int cbComma = commaLength * sizeof(char);
    int cbAnd = andLength * sizeof(char);

    //calculate the sum of the lengths of the strings
    foreach (string s in strings)
    {
        totalLength += s.Length;
        ++numEntries;
    }

    //add to the total length the length of the constant characters
    if (numEntries >= 2)
        totalLength += 5;  // " AND "

    if (numEntries > 2)
        totalLength += (2 * (numEntries - 2)); // ", " between items

    //setup some meta-variables to help later
    secondToLast = numEntries - 2;
    last = numEntries - 1;

    //allocate the memory for the combined string
    combinedString = new char[totalLength];
    //set the first character to {
    combinedString[0] = '{';
    currentPosition = 1;

    if (numEntries > 0)
    {
        //now copy each string into its place
        foreach (string s in strings)
        {
            Buffer.BlockCopy(s.ToCharArray(), 0, combinedString, currentPosition * sizeof(char), s.Length * sizeof(char));
            currentPosition += s.Length;

            if (currentEntry == secondToLast)
            {
                Buffer.BlockCopy(andSeparator, 0, combinedString, currentPosition * sizeof(char), cbAnd);
                currentPosition += andLength;
            }
            else if (currentEntry == last)
            {
                combinedString[currentPosition] = '}'; //set the last character to '}'
                break;  //don't bother making that last call to the enumerator
            }
            else if (currentEntry < secondToLast)
            {
                Buffer.BlockCopy(commaSeparator, 0, combinedString, currentPosition * sizeof(char), cbComma);
                currentPosition += commaLength;
            }

            ++currentEntry;
        }
    }
    else
    {
        //set the last character to '}'
        combinedString[1] = '}';
    }

    return new string(combinedString);
}
于 2009-05-22T20:04:43.247 回答
2

另一个变体 - 为了代码清晰起见,将标点符号和迭代逻辑分开。并且还在考虑性能。

按要求使用纯 IEnumerable/string/ 和列表中的字符串不能为空。

public static string Concat(IEnumerable<string> strings)
{
    return "{" + strings.reduce("", (acc, prev, cur, next) => 
               acc.Append(punctuation(prev, cur, next)).Append(cur)) + "}";
}
private static string punctuation(string prev, string cur, string next)
{
    if (null == prev || null == cur)
        return "";
    if (null == next)
        return " and ";
    return ", ";
}

private static string reduce(this IEnumerable<string> strings, 
    string acc, Func<StringBuilder, string, string, string, StringBuilder> func)
{
    if (null == strings) return "";

    var accumulatorBuilder = new StringBuilder(acc);
    string cur = null;
    string prev = null;
    foreach (var next in strings)
    {
        func(accumulatorBuilder, prev, cur, next);
        prev = cur;
        cur = next;
    }
    func(accumulatorBuilder, prev, cur, null);

    return accumulatorBuilder.ToString();
}

F# 肯定看起来好多了:

let rec reduce list =
    match list with
    | []          -> ""
    | head::curr::[]  -> head + " and " + curr
    | head::curr::tail  -> head + ", " + curr :: tail |> reduce
    | head::[] -> head

let concat list = "{" + (list |> reduce )  + "}"
于 2009-10-09T09:31:05.297 回答
2

迟到:

public static string CommaQuibbling(IEnumerable<string> items)
{
    string[] parts = items.ToArray();
    StringBuilder result = new StringBuilder('{');
    for (int i = 0; i < parts.Length; i++)
    {
        if (i > 0)
            result.Append(i == parts.Length - 1 ? " and " : ", ");
        result.Append(parts[i]);
    }
    return result.Append('}').ToString();
}
于 2010-03-23T05:53:45.050 回答
1
public static string CommaQuibbling(IEnumerable<string> items)
{
  int count = items.Count();
  string answer = string.Empty;
  return "{" + 
      (count==0)  ?  ""  :  
         (  items[0] + 
             (count == 1 ? "" :  
                 items.Range(1,count-1).
                     Aggregate(answer, (s,a)=> s += ", " + a) +
                 items.Range(count-1,1).
                     Aggregate(answer, (s,a)=> s += " AND " + a) ))+ "}";
}

它被实现为,

if count == 0 , then return empty,
if count == 1 , then return only element,
if count > 1 , then take two ranges, 
   first 2nd element to 2nd last element
   last element
于 2009-04-25T08:56:03.340 回答
1

跳过复杂的聚合代码并在构建后清理字符串怎么样?

public static string CommaQuibbling(IEnumerable<string> items)    
{
    var aggregate = items.Aggregate<string, StringBuilder>(
        new StringBuilder(), 
        (b,s) => b.AppendFormat(", {0}", s));
    var trimmed = Regex.Replace(aggregate.ToString(), "^, ", string.Empty);
    return string.Format(
               "{{{0}}}", 
               Regex.Replace(trimmed, 
                   ", (?<last>[^,]*)$", @" and ${last}"));
}

更新:正如评论中所指出的,这不适用于带有逗号的字符串。我尝试了其他一些变体,但没有明确的字符串可以包含什么规则,我将遇到真正的问题,将任何可能的最后一项与正则表达式匹配,这对我来说是一个很好的教训,了解它们的局限性。

于 2009-04-25T09:25:56.310 回答
1

这是我的,但我意识到它与 Marc 的非常相似,在事物的顺序上有一些细微差别,并且我还添加了单元测试。

using System;
using NUnit.Framework;
using NUnit.Framework.Extensions;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework.SyntaxHelpers;

namespace StringChallengeProject
{
    [TestFixture]
    public class StringChallenge
    {
        [RowTest]
        [Row(new String[] { }, "{}")]
        [Row(new[] { "ABC" }, "{ABC}")]
        [Row(new[] { "ABC", "DEF" }, "{ABC and DEF}")]
        [Row(new[] { "ABC", "DEF", "G", "H" }, "{ABC, DEF, G and H}")]
        public void Test(String[] input, String expectedOutput)
        {
            Assert.That(FormatString(input), Is.EqualTo(expectedOutput));
        }

        //codesnippet:93458590-3182-11de-8c30-0800200c9a66
        public static String FormatString(IEnumerable<String> input)
        {
            if (input == null)
                return "{}";

            using (var iterator = input.GetEnumerator())
            {
                // Guard-clause for empty source
                if (!iterator.MoveNext())
                    return "{}";

                // Take care of first value
                var output = new StringBuilder();
                output.Append('{').Append(iterator.Current);

                // Grab next
                if (iterator.MoveNext())
                {
                    // Grab the next value, but don't process it
                    // we don't know whether to use comma or "and"
                    // until we've grabbed the next after it as well
                    String nextValue = iterator.Current;
                    while (iterator.MoveNext())
                    {
                        output.Append(", ");
                        output.Append(nextValue);

                        nextValue = iterator.Current;
                    }

                    output.Append(" and ");
                    output.Append(nextValue);
                }


                output.Append('}');
                return output.ToString();
            }
        }
    }
}
于 2009-04-25T10:19:56.337 回答
1

我非常喜欢乔恩的回答,但那是因为它很像我解决问题的方式。我没有在这两个变量中专门编码,而是在 FIFO 队列中实现了它们。

这很奇怪,因为我只是假设会有 15 个帖子都做了完全相同的事情,但看起来我们是唯一这样做的两个。哦,看看这些答案,Marc Gravell 的答案也非常接近我们使用的方法,但他使用的是两个“循环”,而不是坚持价值观。

但是所有这些使用 LINQ 和正则表达式以及加入数组的答案看起来就像是疯话!:-)

于 2009-04-25T11:04:05.730 回答
1

我不认为使用一个好的旧数组是一种限制。这是我使用数组和扩展方法的版本:

public static string CommaQuibbling(IEnumerable<string> list)
{
    string[] array = list.ToArray();

    if (array.Length == 0) return string.Empty.PutCurlyBraces();
    if (array.Length == 1) return array[0].PutCurlyBraces();

    string allExceptLast = string.Join(", ", array, 0, array.Length - 1);
    string theLast = array[array.Length - 1];

    return string.Format("{0} and {1}", allExceptLast, theLast)
                 .PutCurlyBraces();
}

public static string PutCurlyBraces(this string str)
{
    return "{" + str + "}";
}

我使用数组是因为该string.Join方法以及是否有可能通过索引访问最后一个元素。扩展方法在这里是因为 DRY。

我认为性能损失来自list.ToArray()andstring.Join调用,但我希望这一段代码易于阅读和维护。

于 2009-04-25T12:23:05.213 回答
1

我认为 Linq 提供了相当可读的代码。这个版本在 0.89 秒内处理了一百万个“ABC”:

using System.Collections.Generic;
using System.Linq;

namespace CommaQuibbling
{
    internal class Translator
    {
        public string Translate(IEnumerable<string> items)
        {
            return "{" + Join(items) + "}";
        }

        private static string Join(IEnumerable<string> items)
        {
            var leadingItems = LeadingItemsFrom(items);
            var lastItem = LastItemFrom(items);

            return JoinLeading(leadingItems) + lastItem;
        }

        private static IEnumerable<string> LeadingItemsFrom(IEnumerable<string> items)
        {
            return items.Reverse().Skip(1).Reverse();
        }

        private static string LastItemFrom(IEnumerable<string> items)
        {
            return items.LastOrDefault();
        }

        private static string JoinLeading(IEnumerable<string> items)
        {
            if (items.Any() == false) return "";

            return string.Join(", ", items.ToArray()) + " and ";
        }
    }
}
于 2009-04-25T19:26:16.007 回答
1

您可以使用没有 LINQ 的 foreach、委托、闭包、列表或数组,并且仍然具有可理解的代码。使用 bool 和字符串,如下所示:

public static string CommaQuibbling(IEnumerable items)
{
    StringBuilder sb = new StringBuilder("{");
    bool empty = true;
    string prev = null;
    foreach (string s in items)
    {
        if (prev!=null)
        {
            if (!empty) sb.Append(", ");
            else empty = false;
            sb.Append(prev);
        }
        prev = s;
    }
    if (prev!=null)
    {
        if (!empty) sb.Append(" and ");
        sb.Append(prev);
    }
    return sb.Append('}').ToString();
}
于 2009-04-27T19:18:01.267 回答
1
public static string CommaQuibbling(IEnumerable<string> items)
{
   var itemArray = items.ToArray();

   var commaSeparated = String.Join(", ", itemArray, 0, Math.Max(itemArray.Length - 1, 0));
   if (commaSeparated.Length > 0) commaSeparated += " and ";

   return "{" + commaSeparated + itemArray.LastOrDefault() + "}";
}
于 2009-04-27T20:27:43.487 回答
1

这是我的提交。稍微修改了签名以使其更通用。使用 .NET 4 功能(String.Join()using IEnumerable<T>),否则适用于 .NET 3.5。目标是使用具有显着简化逻辑的 LINQ。

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) + "}";
}
于 2010-10-02T20:16:10.770 回答
1

有几个非 C# 的答案,并且原始帖子确实要求任何语言的答案,所以我想我会展示另一种 C# 程序员似乎没有触及的方法:DSL!

(defun quibble-comma (words)
  (format nil "~{~#[~;~a~;~a and ~a~:;~@{~a~#[~; and ~:;, ~]~}~]~}" words))

精明的人会注意到 Common Lisp 并没有真正IEnumerable<T>内置,因此FORMAT这里只能在适当的列表上工作。但如果你做了一个IEnumerable,你当然也可以扩展FORMAT工作。(Clojure 有这个吗?)

此外,任何阅读这篇文章的有品味的人(包括 Lisp 程序员!)都可能会被"~{~#[~;~a~;~a and ~a~:;~@{~a~#[~; and ~:;, ~]~}~]~}"那里的文字所冒犯。我不会声称它FORMAT实现了一个好的DSL,但我确实相信拥有一些强大的 DSL 来组合字符串是非常有用的。正则表达式是一种强大的 DSL,用于将字符串分开,并且string.Format是一种用于将字符串组合在一起的 DSL(某种),但它非常弱。

我想每个人都在写这类东西。为什么还没有一些内置的通用 DSL 呢?我认为我们最接近的是“Perl”,也许。

于 2010-10-02T20:43:22.767 回答
1

只是为了好玩,使用 C# 4.0 中的新 Zip 扩展方法:

private static string CommaQuibbling(IEnumerable<string> list)
{
    IEnumerable<string> separators = GetSeparators(list.Count());
    var finalList = list.Zip(separators, (w, s) => w + s);
    return string.Concat("{", string.Join(string.Empty, finalList), "}");
}

private static IEnumerable<string> GetSeparators(int itemCount)
{
    while (itemCount-- > 2)
        yield return ", ";

    if (itemCount == 1)
        yield return " and ";

    yield return string.Empty;
}
于 2011-01-25T21:06:38.647 回答
1
return String.Concat(
    "{",
    input.Length > 2 ?
        String.Concat(
            String.Join(", ", input.Take(input.Length - 1)),
            " and ",
            input.Last()) :
    String.Join(" and ", input),
    "}");
于 2011-01-28T14:50:44.600 回答
1

我试过使用foreach。请让我知道你的意见。

private static string CommaQuibble(IEnumerable<string> input)
{
    var val = string.Concat(input.Process(
        p => p,
        p => string.Format(" and {0}", p),
        p => string.Format(", {0}", p)));
    return string.Format("{{{0}}}", val);
}

public static IEnumerable<T> Process<T>(this IEnumerable<T> input, 
    Func<T, T> firstItemFunc, 
    Func<T, T> lastItemFunc, 
    Func<T, T> otherItemFunc)
{
    //break on empty sequence
    if (!input.Any()) yield break;

    //return first elem
    var first = input.First();
    yield return firstItemFunc(first);

    //break if there was only one elem
    var rest = input.Skip(1);
    if (!rest.Any()) yield break;

    //start looping the rest of the elements
    T prevItem = first;
    bool isFirstIteration = true;
    foreach (var item in rest)
    {
        if (isFirstIteration) isFirstIteration = false;
        else
        {
            yield return otherItemFunc(prevItem);
        }
        prevItem = item;
    }

    //last element
    yield return lastItemFunc(prevItem);
}
于 2012-08-15T05:26:56.653 回答
1

以下是基于http://blogs.perl.org/users/brian_d_foy/2013/10/comma-quibbling-in-perl.html上的回复用 Perl 编写的几个解决方案和测试代码。

#!/usr/bin/perl

use 5.14.0;
use warnings;
use strict;
use Test::More qw{no_plan};

sub comma_quibbling1 {
   my (@words) = @_;
   return "" unless @words;
   return $words[0] if @words == 1;
   return join(", ", @words[0 .. $#words - 1]) . " and $words[-1]";
}

sub comma_quibbling2 {
   return "" unless @_;
   my $last = pop @_;
   return $last unless @_;
   return join(", ", @_) . " and $last";
}

is comma_quibbling1(qw{}),                   "",                         "1-0";
is comma_quibbling1(qw{one}),                "one",                      "1-1";
is comma_quibbling1(qw{one two}),            "one and two",              "1-2";
is comma_quibbling1(qw{one two three}),      "one, two and three",       "1-3";
is comma_quibbling1(qw{one two three four}), "one, two, three and four", "1-4";

is comma_quibbling2(qw{}),                   "",                         "2-0";
is comma_quibbling2(qw{one}),                "one",                      "2-1";
is comma_quibbling2(qw{one two}),            "one and two",              "2-2";
is comma_quibbling2(qw{one two three}),      "one, two and three",       "2-3";
is comma_quibbling2(qw{one two three four}), "one, two, three and four", "2-4";
于 2013-10-14T20:45:34.150 回答
0

距离上一篇文章还不到十年,所以这是我的变化:

    public static string CommaQuibbling(IEnumerable<string> items)
    {
        var text = new StringBuilder();
        string sep = null;
        int last_pos = items.Count();
        int next_pos = 1;

        foreach(string item in items)
        {
            text.Append($"{sep}{item}");
            sep = ++next_pos < last_pos ? ", " : " and ";
        }

        return $"{{{text}}}";
    }
于 2018-08-15T20:34:08.503 回答
0

在一份声明中:

public static string CommaQuibbling(IEnumerable<string> inputList)
{
    return
        String.Concat("{",
            String.Join(null, inputList
                .Select((iw, i) =>
                    (i == (inputList.Count() - 1)) ? $"{iw}" :
                        (i == (inputList.Count() - 2) ? $"{iw} and " : $"{iw}, "))
                    .ToArray()), "}");
}
于 2020-08-29T21:49:53.347 回答
0

.NET Core我们可以利用SkipLast和TakeLast

public static string CommaQuibblify(IEnumerable<string> items)
{
    var head = string.Join(", ", items.SkipLast(2).Append(""));
    var tail = string.Join(" and ", items.TakeLast(2));
    return '{' + head + tail + '}';
}

https://dotnetfiddle.net/X58qvZ

于 2020-08-29T23:38:53.633 回答