Задача Эрика Липперта "запятая", лучший ответ? - PullRequest
23 голосов
/ 25 апреля 2009

Я хотел довести эту проблему до сведения сообщества stackoverflow. Исходная проблема и ответы: здесь . Кстати, если вы не следили за этим раньше, вы должны попытаться прочитать блог Эрика, это чистая мудрость.

Резюме:

Напишите функцию, которая принимает ненулевое значение IEnumerable и возвращает строку со следующими характеристиками:

  1. Если последовательность пуста, в результате получается строка "{}".
  2. Если последовательность представляет собой один элемент «ABC», тогда полученная строка будет «{ABC}».
  3. Если последовательность представляет собой последовательность из двух элементов «ABC», «DEF», то полученная строка будет «{ABC and DEF}».
  4. Если последовательность содержит более двух элементов, скажем, «ABC», «DEF», «G», «H», то полученная строка будет «{ABC, DEF, G и H}». (Примечание: запятая не оксфордская!)

Как вы можете видеть, даже наш собственный Джон Скит (да, хорошо известно, что он может быть в двух местах одновременно ) опубликовал решение, но его (ИМХО) нет самый элегантный, хотя, вероятно, вы не можете побить его производительность.

Что ты думаешь? Там есть довольно хорошие варианты. Мне действительно нравится одно из решений, которое включает методы выбора и агрегирования (от Фернандо Николе). Linq очень силен и посвящает некоторое время таким вызовам, чтобы вы многому научились. Я немного изменил его, чтобы он был более производительным и понятным (используя Count и избегая Reverse):

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

Ответы [ 25 ]

1 голос
/ 15 октября 2013

Вот пара решений и тестового кода, написанного на Perl на основе ответов на 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";
1 голос
/ 15 августа 2012

Я пытался использовать foreach. Пожалуйста, дайте мне знать ваше мнение.

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);
}
1 голос
/ 27 апреля 2009

Вы можете использовать foreach без LINQ, делегатов, замыканий, списков или массивов, и при этом иметь понятный код. Используйте bool и строку, например, так:

public static string CommaQuibbling(IEnumerable items)
{
    StringBuilder sb = new StringBuilder("{");
    bool empty = true;
    string prev = null;
    foreach (string s in items)
    {
        if (prev!=null)
        {
            if (!empty) sb.Append(", ");
            else empty = false;
            sb.Append(prev);
        }
        prev = s;
    }
    if (prev!=null)
    {
        if (!empty) sb.Append(" and ");
        sb.Append(prev);
    }
    return sb.Append('}').ToString();
}
1 голос
/ 28 апреля 2009
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 голосов
/ 15 августа 2018

Прошло уже не одно десятилетие с момента последнего поста, поэтому вот мой вариант:

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