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

33voto

dbkk Punkte 12240

Ineffizient, aber ich denke klar.

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

Wenn ich den Code betreuen würde, würde ich dies einer clevereren Version vorziehen.

28voto

Marc Gravell Punkte 970173

Wie wäre es mit diesem Ansatz? Rein kumulativ - kein Back-Tracking, und iteriert nur einmal. Für rohe Leistung, ich bin nicht sicher, Sie tun besser mit LINQ usw., unabhängig davon, wie "schön" eine LINQ-Antwort sein könnte.

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

5voto

Anton Tykhyy Punkte 18869

Wenn ich viel mit Datenströmen arbeiten würde, die Erst- und Letztinformationen erfordern, würde ich diese Erweiterung verwenden:

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

Dann ist die einfachste (nicht die schnellste, dafür wären einige weitere praktische Erweiterungsmethoden erforderlich) Lösung die folgende:

public static string Quibble (IEnumerable<string> strings)
{
    return "{" + String.Join ("", strings.MapWithPositions ((pos, item) => (
       (pos &  StreamPosition.First) != 0      ? "" : 
        pos == StreamPosition.Last   ? " and " : ", ") + item)) + "}" ;
}

3voto

John La Rooy Punkte 278961

Hier als Python One-Liner

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

Diese Version ist vielleicht leichter zu verstehen

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

2voto

Norman Ramsey Punkte 193087

Ich bin ein Fan der Kommasetzung: Ich esse, schieße und gehe.

Ich brauche ständig eine Lösung für dieses Problem und habe es in 3 Sprachen gelöst (allerdings nicht in C#). Ich würde die folgende Lösung anpassen (in Lua nicht in geschweifte Klammern verpackt), indem Sie eine concat Methode, die mit jeder 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

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