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

jrw32982 Punkte 568

Hier sind ein paar Lösungen und ein in Perl geschriebener Testcode auf der Grundlage der Antworten unter http://blogs.perl.org/users/brian_d_foy/2013/10/comma-quibbling-in-perl.html .

#!/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";

1voto

prashanth Punkte 2029

Ich habe versucht, foreach zu verwenden. Bitte teilen Sie mir Ihre Meinung mit.

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

1voto

abatishchev Punkte 94886
return String.Concat(
    "{",
    input.Length > 2 ?
        String.Concat(
            String.Join(", ", input.Take(input.Length - 1)),
            " and ",
            input.Last()) :
    String.Join(" and ", input),
    "}");

1voto

Nur zum Spaß, unter Verwendung der neuen Zip-Erweiterungsmethode von C# 4.0:

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

1voto

Ken Punkte 1149

Es gibt ein paar Nicht-C#-Antworten, und der ursprüngliche Beitrag fragte nach Antworten in jeder Sprache, also dachte ich, ich zeige einen anderen Weg, um es zu tun, dass keiner der C#-Programmierer scheint auf berührt haben: eine DSL!

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

Der Scharfsinnige wird feststellen, dass Common Lisp nicht wirklich eine IEnumerable<T> eingebaut, und daher FORMAT funktioniert hier nur bei einer richtigen Liste. Aber wenn Sie eine IEnumerable könnten Sie sicherlich erweitern FORMAT um auch daran zu arbeiten. (Verfügt Clojure über so etwas?)

Außerdem: Jeder, der dies liest und Geschmack (einschließlich Lisp-Programmierer!) werden sich wahrscheinlich an der wörtlichen "~{~#[~;~a~;~a and ~a~:;~@{~a~#[~; and ~:;, ~]~}~]~}" dort. Ich werde nicht behaupten, dass FORMAT implementiert eine gut DSL, aber ich glaube, dass es ungemein nützlich ist, über einige leistungsfähige DSL zum Zusammenstellen von Zeichenketten. Regex ist eine mächtige DSL, um Zeichenketten zu zerlegen, und string.Format ist eine DSL (eine Art) für das Zusammenstellen von Strings, aber sie ist dumm und schwach.

Ich glaube, jeder schreibt diese Art von Dingen die ganze Zeit. Warum zum Teufel gibt es noch keine eingebaute, universelle, geschmackvolle DSL für so etwas? Ich glaube, am ehesten haben wir "Perl", vielleicht.

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