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

后端 未结 27 2184
日久生厌
日久生厌 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:28

    Disclaimer: I used this as an excuse to play around with new technologies, so my solutions don't really live up to the Eric's original demands for clarity and maintainability.

    Naive Enumerator Solution (I concede that the foreach variant of this is superior, as it doesn't require manually messing about with the enumerator.)

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

    Solution using 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();
                });
    }
    

    Solution with TPL

    This solution uses a producer-consumer queue to feed the input sequence to the processor, whilst keeping at least two elements buffered in the queue. Once the producer has reached the end of the input sequence, the last two elements can be processed with special treatment.

    In hindsight there is no reason to have the consumer operate asynchronously, which would eliminate the need for a concurrent queue, but as I said previously, I was just using this as an excuse to play around with new technologies :-)

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

    Unit tests elided for brevity.

    0 讨论(0)
  • 2020-12-01 07:28

    I don't think that using a good old array is a restriction. Here is my version using an array and an extension method:

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

    I am using an array because of the string.Join method and because if the possibility of accessing the last element via an index. The extension method is here because of DRY.

    I think that the performance penalities come from the list.ToArray() and string.Join calls, but all in one I hope that piece of code is pleasent to read and maintain.

    0 讨论(0)
  • 2020-12-01 07:28

    I think Linq provides fairly readable code. This version handles a million "ABC" in .89 seconds:

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