23 Stimmen

Eric Lipperts Herausforderung "Kommaspielerei", beste Antwort?

Ich wollte die Aufmerksamkeit der Stackoverflow-Community auf diese Herausforderung lenken. Das ursprüngliche Problem und die Antworten sind aquí . Übrigens, wenn Sie ihn bisher nicht verfolgt haben, sollten Sie versuchen, Erics Blog zu lesen, er ist pure Weisheit.

Zusammenfassung:

Schreiben Sie eine Funktion, die eine nicht-null IEnumerable annimmt und einen String mit den folgenden Eigenschaften zurückgibt:

  1. Wenn die Sequenz leer ist, lautet die resultierende Zeichenfolge "{}".
  2. Handelt es sich bei der Sequenz um ein einzelnes Element "ABC", lautet die resultierende Zeichenfolge "{ABC}".
  3. Handelt es sich bei der Sequenz um die Zweierfolge "ABC", "DEF", so lautet die resultierende Zeichenfolge "{ABC und DEF}".
  4. Wenn die Sequenz mehr als zwei Elemente enthält, z. B. "ABC", "DEF", "G", "H", dann ist die resultierende Zeichenfolge "{ABC, DEF, G und H}". (Hinweis: kein Oxford-Komma!)

Wie Sie sehen können, hat sogar unser eigener Jon Skeet (ja, es ist bekannt, dass er kann an zwei Orten gleichzeitig sein ) hat eine Lösung gepostet, aber seine (IMHO) ist nicht die eleganteste, obwohl ihre Leistung wahrscheinlich nicht zu schlagen ist.

Was meinen Sie dazu? Es gibt da ziemlich gute Möglichkeiten. Eine der Lösungen, die Select- und Aggregate-Methoden (von Fernando Nicolet) beinhaltet, gefällt mir sehr gut. Linq ist sehr leistungsfähig, und wenn man sich solchen Herausforderungen widmet, lernt man eine Menge. Ich habe es ein bisschen verdreht, damit es etwas leistungsfähiger und übersichtlicher ist (indem ich Count verwende und Reverse vermeide):

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

1voto

Theo Lenndorff Punkte 4438

Ich glaube nicht, dass die Verwendung eines guten alten Arrays eine Einschränkung darstellt. Hier ist meine Version mit einem Array und einer Erweiterungsmethode:

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

Ich verwende ein Array wegen der string.Join Methode und wegen der Möglichkeit des Zugriffs auf das letzte Element über einen Index. Die Erweiterungsmethode ist hier wegen der DRY.

Ich denke, die Leistungseinbußen ergeben sich aus den list.ToArray() y string.Join Aufrufe, aber alles in allem hoffe ich, dass dieses Stück Code angenehm zu lesen und zu pflegen ist.

1voto

tsimon Punkte 8182

Jons Antwort hat mir sehr gut gefallen, aber das liegt daran, dass sie meiner Herangehensweise an das Problem sehr ähnlich ist. Anstatt die beiden Variablen speziell zu kodieren, habe ich sie in einer FIFO-Warteschlange implementiert.

Es ist seltsam, denn ich bin davon ausgegangen, dass es 15 Beiträge gibt, die alle genau das Gleiche tun, aber es sieht so aus, als wären wir die einzigen, die es so machen. Oh, wenn ich mir diese Antworten ansehe, kommt Marc Gravells Antwort dem Ansatz, den wir verwendet haben, ziemlich nahe, aber er verwendet zwei "Schleifen", anstatt Werte zu behalten.

Aber all diese Antworten mit LINQ und Regex und dem Verbinden von Arrays scheinen nur verrücktes Gerede zu sein! :-)

1voto

Lasse V. Karlsen Punkte 364542

Hier ist meiner, aber ich weiß, dass er dem von Marc ziemlich ähnlich ist, mit einigen kleinen Unterschieden in der Reihenfolge der Dinge, und ich habe auch Unit-Tests hinzugefügt.

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

1voto

MichaC Punkte 2804

Wie wäre es, wenn Sie den komplizierten Aggregationscode überspringen und die Zeichenfolge nach der Erstellung einfach bereinigen?

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

AKTUALISIERT: Dies funktioniert nicht mit Zeichenketten mit Kommas, wie in den Kommentaren erwähnt wurde. Ich habe einige andere Varianten ausprobiert, aber ohne eindeutige Regeln darüber, was die Zeichenfolgen enthalten können, werde ich echte Probleme haben, jedes mögliche letzte Element mit einem regulären Ausdruck abzugleichen, was dies zu einer schönen Lektion für mich über ihre Grenzen macht.

0voto

Mike Punkte 440

Es ist noch nicht ganz ein Jahrzehnt seit dem letzten Beitrag vergangen, also hier meine Variante:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X