Eric Lippert's challenge “comma-quibbling”, best answer?

后端 未结 27 2182
日久生厌
日久生厌 2020-12-01 06:59

I wanted to bring this challenge to the attention of the stackoverflow community. The original problem and answers are here. BTW, if you did not follow it before, you should

相关标签:
27条回答
  • 2020-12-01 07:06
    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() + "}";
    }
    
    0 讨论(0)
  • 2020-12-01 07:09

    This isn't brilliantly readable, but it scales well up to tens of millions of strings. I'm developing on an old Pentium 4 workstation and it does 1,000,000 strings of average length 8 in about 350ms.

    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);
    }
    
    0 讨论(0)
  • 2020-12-01 07:09
    return String.Concat(
        "{",
        input.Length > 2 ?
            String.Concat(
                String.Join(", ", input.Take(input.Length - 1)),
                " and ",
                input.Last()) :
        String.Join(" and ", input),
        "}");
    
    0 讨论(0)
  • 2020-12-01 07:09

    In one statement:

    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()), "}");
    }
    
    0 讨论(0)
  • 2020-12-01 07:11

    Here's mine, but I realize it's pretty much like Marc's, some minor differences in the order of things, and I added unit-tests as well.

    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();
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-01 07:12

    I'm a fan of the serial comma: I eat, shoot, and leave.

    I continually need a solution to this problem and have solved it in 3 languages (though not C#). I would adapt the following solution (in Lua, does not wrap answer in curly braces) by writing a concat method that works on any IEnumerable:

    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
    
    0 讨论(0)
提交回复
热议问题