Генерация одной строки с оксфордской запятой из списка


24

Каковы некоторые умные (краткие и идиоматические) подходы к получению списка строк и возвращению единственной правильно пунктуированной строки, построенной из списка, с каждым элементом в кавычках.

Это пришло мне в голову при экспериментировании с Groovy , для которого мое слишком буквальное, но иллюстративное решение

def temp = things.collect({"\'${it}\'"})
switch (things.size()) {
    case 1:
        result = temp[0]
        break
    case 2:
        result = temp.join(" and ")
        break
    default:
        result = temp.take(temp.size()-1).join(", ") + ", and " + temp[-1]
        break
}

То есть, ['1']должен уступить '1', ['1','2']должен уступить '1 and 2', [посмотрите, что я там сделал?] И ['1','2','3']должен уступить '1, 2, and 3'.

У меня есть несколько хороших ответов на Groovy, но я бы хотел посмотреть, что могут делать другие языки.

Каковы некоторые компактные умные подходы на разных языках, которые используют преимущества и особенности этих языков?


6
Добро пожаловать в PPCG. Обычно вопросы, размещенные здесь, являются вызовами для сообщества. Как таковые, им нужны объективные критерии победы. Я полагаю, что этот вопрос достаточно хорошо подходит для того, чтобы стать вызовом для игры в гольф . Вы можете пометить это как таковое? Если это так, я думаю, вам следует немного ужесточить спецификации ввода и вывода.
Цифровая травма

7
Это было бы более интересно с реальными предложениями :['we invited the stripper','JFK','Stalin']
ThisSuitIsBlackNot

1
Можем ли мы предположить, что сами строки уже не содержат запятых?
Мартин Эндер

2
Задача должна была быть озаглавлена ​​"Кто дает ---- об оксфордской запятой ?"
Игби Крупный человек

3
@OldCurmudgeon Я думаю, что вы имеете в виду "американизированный мусор";)
ThisSuitIsBlackNot

Ответы:


76

CSS, 132 116 115 байт

a:not(:last-child):nth-child(n+2):after,a:nth-last-child(n+3):after{content:","}a+:last-child:before{content:"and "

CSS не часто встречается в гольф-коде, потому что он может только форматировать текст, но на самом деле он работает для этой задачи, и я подумал, что это будет интересно. Посмотрите его в действии, используя приведенный выше фрагмент (нажмите «Показать фрагмент кода»).

Список должен быть в связанном HTML-файле, где каждый элемент окружен <a>тегами и разделен переносами строк. Элементы списка должны быть единственными элементами в их родительском элементе, например

<a>one</a>
<a>two</a>
<a>three</a>

объяснение

a:not(:last-child):nth-child(n+2)::after,
a:nth-last-child(n+3)::after {
    content: ",";
}

a + :last-child::before {
    content: "and ";
}

Давайте рассмотрим вышедшую из строя версию выше. Если вы не знакомы с тем, как работает CSS, все, что находится вне фигурных скобок, является селектором, который определяет набор элементов HTML, к которым применяются объявления внутри фигурных скобок. Каждая пара объявление-селектор называется правилом . (Это более сложно, но этого будет достаточно для этого объяснения.) Перед применением какого-либо стиля, список кажется разделенным только пробелами.

Мы хотим добавлять запятые после каждого слова, кроме последнего, за исключением списков из двух слов, которые не имеют запятых. Первый селектор, a:not(:last-child):nth-child(n+2):afterвыбирает все элементы, кроме первого и последнего. :nth-child(n+2)это более короткий способ сказать :not(:first-child), и он в основном работает, выбирая элементы, индекс которых (начиная с 1) больше или равен 2. (Да, это все еще немного смущает меня. Документы MDN могут помочь.)

Теперь нам просто нужно выбрать первый элемент, чтобы получить запятую, если всего три или более элементов. a:nth-last-child(n+3):afterработает как :nth-child, но с обратной стороны, поэтому он выбирает все элементы, кроме двух последних. Запятая принимает объединение двух наборов, и мы используем :after псевдоэлемент для добавления contentсразу после каждого выбранного элемента.

Второе правило проще. Нам нужно добавить «и» перед последним элементом в списке, если это не единственный элемент. Другими словами, нам нужно выбрать последний элемент, которому предшествует другой элемент. +это соседний селектор в CSS.


1
Brilliant! Я люблю это. : D
COTO

Если бы вы только использовали <li>.
Slebetman

1
<li>было бы идеально, но это добавило бы два дополнительных символа к селекторам. Я выбрал, <a>потому что это одна буква и не применяет свое собственное форматирование.
NinjaBearMonkey

Вау ... это очень впечатляет. Премия за ум.

CSS как лучший ответ? Ницца.
Брэндон

13

Haskell: 81, 77 74 символа

f[x]=x
f[x,y]=x++" and "++y
f[x,y,z]=x++", "++f[y++",",z]
f(x:y)=x++", "++f y

Особенности Haskell: сопоставление с образцом


Вы можете удалить некоторые пробелы
Ray

1
Вы можете просто удалитьf[x,y,z]
seequ

И теперь, это в значительной степени стандартная рекурсия. :)
seequ

Вы забыли оксфордскую запятую и пробелы после запятых - f["a","b","c"]должно быть, "a, b, and c"но это так"a,b and c"
гордый haskeller

4
как насчет игры в гольф, заменив y++", and "++zна f[y++",",z]:)
гордый haskeller

7

Рубин, 47 байтов

q=" and ";p$*[2]?(a=$*.pop;$**", "+?,+q+a):$**q

объяснение

  • Входные данные являются аргументами командной строки ($* ).
  • Когда $*есть третий элемент ( $*[2]не возвращает ноль), возьмите все элементы минус последний и превратите их в разделенную запятыми строку, используяArray#* . Наконец добавьте дополнительную запятую, строку " and "и последний аргумент командной строки.
  • Когда $*нет третьего элемента, были заданы два, один или нулевой аргумент. Аргументы могут безопасно соединяться со строкой " and "и давать правильный результат.

6

Python 2 (61)

s=input()
d=s.pop()
print", ".join(s)+", and "[8-7*len(s):]+d

Основной трюк состоит в том, чтобы отрезать часть окончательного столяра ", and "для одного и двух элементов. Для одного все это вырезано, а для двух запятая удалена. Это делается путем нарезки [8-7*len(s):](отмечая, чтоs это на один короче после pop).

К сожалению, dнельзя просто заменить его выражением, иначе popэто произойдет слишком поздно.


Вы могли бы заменить первый sс s=input()и удалить первую строку, если я не ошибаюсь. Сохраняет 2 символа.
Томсминг

@ tomsmeding Я не понимаю, что ты предлагаешь. Код ссылается на sнесколько раз.
xnor

Подожди, я делаю странные вещи этим утром. Забудь про это. :)
Томсминг

6

CSS, 62 символа 112 символов

Вдохновлен другими записями, даже короче. Обратите внимание, что требуется, чтобы элементы A не были разделены пробелами:

a+a:before{content:", "}a+a:last-child:before{content:", and "

http://jsfiddle.net/olvlvl/1Ls79ocb/

Исправьте для «один и два», как указано Деннисом:

a+a:before{content:", "}a+a:last-child:before{content:", and "}a:first-child+a:last-child:before{content:" and "

http://jsfiddle.net/olvlvl/1Ls79ocb/3/


5

Perl (v 5.10+) - 37 35 34 28

@F>2&&s/ /, /g;s/.* \K/and /

чтобы работать с perl -ape, с список прилагается пространство , отделенное от STDIN.

Выход для различных входов:

$ perl -ape '@F>2&&s/ /, /g;s/.* \K/and /'
oxford
oxford
oxford cambridge
oxford and cambridge
oxford cambridge comma
oxford, cambridge, and comma
oxford cambridge comma space
oxford, cambridge, comma, and space

Вы можете уменьшить это на 3 байта, изменив свою последнюю замену наs/(\S+)$/and $1/
ThisSuitIsBlackNot

1
@ThisSuitIsBlackNot Вы можете отбросить '\ n' (спасибо), но вы не можете удалить пробелы, или ввод "x" становится "и x"
меньше

Oops, should have tested with more inputs. Anyway, you forgot the $ anchor in your edit, so an input of foo bar baz becomes foo, and bar, baz. Also, you can remove one of the spaces by doing s/( \S+)$/ and$1/
ThisSuitIsBlackNot

My last comment still leaves you at 35, but you can write $#F>1 as @F>2 to shave off one more. Sorry for all the suggestions for micro improvements, I just really like this answer :)
ThisSuitIsBlackNot

@ThisSuitIsBlackNot not at all - microedits welcome. I am secretly competing with 28 bytes of CJam and 30 bytes of Golfscript. However I did get to 34 without doing the translation of your comment before last (tested with echo -n ... | wc -c). If you miss the space before (\S+) you'd get to 33 characters, but if you put in test this you will get test and this (two spaces after test), so without more I think that's wrong.
abligh

4

Javascript (63)

l=a.length;l>2&&(a[l-1]='and '+a[l-1]);a.join(l>2?', ':' and ')

случаи:

  • a = [1] => 1
  • a = [1, 2] => 1 and 2
  • a = [1, 2, 3] => 1, 2, and 3

Предостережение: это изменит последний элемент в массиве с длиной> 2.


2
Nice use of &&
Chris Bloom

4

GNU sed - 69 chars including 1 for -r flag

s/ /, /g
s/( [^ ]+)( [^ ]+)$/\1 and\2/
s/^([^,]+),([^,]+)$/\1 and\2/

Takes a space-separated list (fairly idiomatic for shell scripts).

Example

$ sed -r -f oxfordcomma.sed <<< "1"
1
$ sed -r -f oxfordcomma.sed <<< "1 2"
1 and 2
$ sed -r -f oxfordcomma.sed <<< "1 2 3"
1, 2, and 3
$

Yes, let's go with code-golf.
orome

The second output should be 1 and 2 instead of 1, 2
Optimizer

@Optimizer - quite right. Fixed.
Digital Trauma

3

Python 2 - 71, 70 68

s=input()
l=len(s)-1
print', '.join(s[:l])+', and '[l<2:]*(l>0)+s[l]

Character count including both, input and print.


3

Ruby, 57 bytes

f=->l{s=l*', ';s.sub(/,(?!.*,)/,(l.size<3?'':?,)+' and')}

I'm joining the string with , and then I'm replacing the last comma in the string with an and (and optional comma depending on list length).


3

Cobra - 60

do(l as String[])=l.join(', ',if(l.length<3,'',',')+' and ')

Cobra's List<of T>.join function lets you specify a different separator for the final two elements of the list, which is what makes this so short.


3

Perl - 59

sub f{$a=join', ',@_;$#_&&substr$a,(@_>2)-3,@_<3,' and';$a}

Joins the list with commas, then if the list has more than one element, either adds ' and' after the last comma (if length >= 3), or replaces the last comma with it (if length == 2).


3

PHP, 192 167 146 136 characters:

$n=' and ';$s=count($a);echo ($s<3)?join($n,$a):join(', ',array_merge(array_slice($a,0,$s-2),Array(join(",$n",array_slice($a,-2,2)))));

Based on a function I wrote years ago at http://www.christopherbloom.com/2011/05/21/join-implode-an-array-of-string-values-with-formatting/


Can you please put your code (with Oxford commas) into the answer text?

Yes, sorry. I was on my phone and it wouldn't paste the code properly. I'll update from my desktop
Chris Bloom

@Chrisbloom7 even if it is trivial to add, the answer should include a working code, not one that can be made to be one. also, because the objective is to make the code as smaller as possible, not posting working code makes your score non existing.
proud haskeller

Apologies again. First time poster at CG. I've fixed my answer
Chris Bloom

3

Batch - 151 Bytes

@echo off&set f=for %%a in (%~1)do
%f% set/aa+=1
%f% set/ac+=1&if !c!==1 (set o=%%a)else if !c!==!a! (set o=!o!, and %%a)else set o=!o!, %%a
echo !o!

Note; you have to call the script from cmd with the /v switch set as on, this is so I don't have to include the lengthy setLocal enableDelayedExpansion in the script. Otherwise add 30 to the byte count, and call the script normally.

h:\uprof>cmd /von /c test.bat "1 2 3"
1, 2, and 3

h:\uprof>cmd /von /c test.bat "1 2 3 4 5"
1, 2, 3, 4, and 5

3

Groovy, 47 43 57 characters , JavaScript ES6 56 characters

Groovy:

(a[1]?a[0..-2].join(", ")+(a[2]?",":"")+" and ":"")+a[-1]

Since the array is filled with characters, we can replace a.size>1 by a[1]

JavaScript, ES6:

a.join(', ').replace(/,([^,]+)$/,`${a[2]?',':''} and$1`)

Both cases assume that variable a has the array in question.


6
Оксфордская запятая - это запятая перед соединением.
Деннис

3

PHP, 86 84 chars

$a = ['one', 'two', 'three', 'four', 'five'];

После инициализации массива мы начинаем считать:

$L=count($a)-1;$S=', ';$L<2?($S=' and '):($a[$L]='and '.$a[$L]);echo implode($S,$a);

Prettified:

$last_index = count($a) - 1;
$separator = ', ';
if ($last_index < 2) {
    $separator = ' and ';
} else {
    $a[$last_index] = 'and '.$a[$last_index];
}
echo implode($separator, $a);

Последний элемент в списке изменен. Это должно быть хорошо, потому что в PHP, присваивание массивов и вызовы функций выполняют копии.


3

CJam, 35 30 28 27 байт

q~)\_"and "L?@+a+_,2=", ">*

Это программа, которая читает из STDIN и печатает в STDOUT. Попробуйте онлайн.

Как это работает

q~                                     " Q := eval(input())                               ";
  )                                    " P := Q.pop()                                     ";
   \_"and "L?@+                        " P := (Q ? 'and ' : '') + P                       ";
                a+                     " Q += [P]                                         ";
                  _,2=", ">            " J := ', '[(len(Q) == 2):]                        ";
                           *           " R := J.join(Q)                                   ";
                                       " print R (implicit)                               ";

Пример запуска

$ cjam <(echo 'q~)\_"and "L?@+a+_,2=", ">*') <<< '["1"]'; echo
1
$ cjam <(echo 'q~)\_"and "L?@+a+_,2=", ">*') <<< '["1""2"]'; echo
1 and 2
$ cjam <(echo 'q~)\_"and "L?@+a+_,2=", ">*') <<< '["1""2""3"]'; echo
1, 2, and 3
$ cjam <(echo 'q~)\_"and "L?@+a+_,2=", ">*') <<< '["1""2""3""4"]'; echo
1, 2, 3, and 4

Я на хвост - смотрите ниже :-)
abligh

3

Google Sheets, 67 68 67 92 байта

=SUBSTITUTE(JOIN(", ",FILTER(A:A,A:A<>"")),",",IF(COUNTA(A:A)>2,",","")&" and",COUNTA(A:A)-1

Ввод начинается с ячейки A1и продолжается до тех пор, пока существует много записей.
JOINобъединяет их все в строку с запятой между ними.
FILTERудаляет все пустые поля, чтобы в конце не было бесконечных запятых.
SUBSTITUTEзаменяет последнюю запятую (найденаCOUNTA подсчета непустых вводов).

Это не очень интересно, но выполнимо в одной клетке.


Это оставляет оксфордскую запятую.
Зонт

@ Зонт Ну, это было глупо с моей стороны, не так ли? +1 байт
тост инженера

Вы должны быть в состоянии отбросить терминал )этой формулы на -1 байт;
Тейлор Скотт

Два примечания после тестирования этого - A:A>""должны быть преобразованы в, A:A<>""и это не реализует опущение оксфордской запятой в случаях только 2 объектов
Тейлор Скотт

@TaylorScott Я думаю, что я когда-либо только проверял с текстом и пропустил проблему с >"". Поправка, сделанная мной ранее, явно не проверялась. Мне не нравится направление, которое пошло ...
Тост инженера

2

JavaScript (ES6) 60

Предполагая, что во входном массиве нет пустых строк

f=(a,b=a.pop())=>a[0]?a.join(', ')+(a[1]?',':'')+' and '+b:b

Тест In FireFox / Консоль Firebug

console.log(f(['We invited the stripper','JFK','Stalin']))

Выход

 We invited the stripper, JFK, and Stalin

Как мне это запустить? Я попробовал это node --harmonyс массивом строк var a = 'abcdefgh'.split('');, но он просто сидит там с ...и, кажется, ничего не делает. Также +1 за использование уникальной функции ES6 ()=>.
Адриан

@Adrian функция возвращает строку без вывода. Я добавлю тест в ответ.
edc65

Я думаю, что f=(a,b=a.pop())=>a[0]?a.join(', ')+', and '+b:bэто также правильно и на 13 байт короче.
Инго Бюрк

1
@ IngoBürk, который вырезает часть, которая обрабатывает запятую, имеющую 2 элемента. Угадай, что? С 2 пунктами вывод неправильный.
edc65

@ edc65 Ах, да. Может плохо.
Инго Бюрк

2

JavaScript - 60 56 71 67 63

Не совсем идиоматический подход, но мне было весело писать его, и я люблю регулярные выражения.

Предполагая, что массив хранится в var a:

a.join((a.length>2?',':'')+' ').replace(/([^,\s])$/,"and $1")

Сокращается простой проверкой индекса [2]: (yay логическое приведение)

a.join((!!a[2]?',':'')+' ').replace(/([^,\s])$/,'and $1')

Видимо, я отстой на тестировании и пропустил однократный тест. Вот исправленная версия:

a.join((!!a[2]?',':'')+' ').replace(/([^,\s])$/,(!!a[1]?'and ':'')+'$1')

Скинул 2 символа, перевернув мои логические значения, и еще 3, переместив пространство из конкатенации в тогда / еще первую тройную:

a.join((!a[2]?' ':', ')).replace(/([^,\s])$/,(!a[1]?'':'and ')+'$1')

Спасибо @tomsmeding за напоминание о том, что мне не нужно приводить мои логические значения, потому что JS делает это для меня. Я также понял, что забыл удалить скобки, отделяющие первую троичную от конкатенации внутри join():

a.join(a[2]?', ':' ').replace(/([^,\s])$/,(a[1]?'and ':'')+'$1')

Я правильно сделал? Обязательные новые извинения гольфиста.


1
Любящее регулярное выражение известно как мазохизм.
Seequ

На самом деле вам не нужно !!вообще; a[2]?A:Bработает уже. Возможно , придется перевернуть ваши булевы снова .
Томсминг

@ tomsmeding - вы правы. Я всегда настороженно отношусь к булевому поведению JavaScript по умолчанию, поэтому у меня появилась привычка вручную приводить мои булевы значения ...
Adrian

@ Adrian Correction: для любого значения, кроме пустой строки, a[i]равно true . Ну, почти. Я предполагаю, что если вы получите 2 входа, ваш массив имеет длину два. Тогда a[2]===undefinedи undefinedоцениваю false. РЕДАКТИРОВАТЬ: ваше редактирование - то, что я имел в виду :)
tomsmeding

2

Golfscript - 31 39 34 30

Привет, мир! Я долгое время скрывался, впервые пишу. Вот мое 30-байтовое решение в Golfscript (учитывая, что массив, такой как [1 2 3], уже находится в стеке).

.,3<" and ":A{~A(;\+]", "}if*

Результаты подходят для всех тестовых случаев.

РЕДАКТИРОВАТЬ: Возьми это, CJam!


Отредактировано для учета массива длины 1.
Джозия Уинслоу

Отредактировано для более эффективного алгоритма.
Джозия Уинслоу

Битва продолжается. : P
Деннис

Ах, но теперь мы даже. : P
Джозия Уинслоу

2

Xojo, 52 71 83 символов

dim i as int8=ubound(L)
L(i)=if(i=0,"","and ")+L(i)
Return L.Join(if(i=1," ",", ")

Обратите внимание, что UBound на единицу меньше длины массива.


Это, кажется, добавляет запятую, когда есть 2 пункта - не должно
edc65

Хороший улов. Отредактировано, чтобы исправить.
серебрянный пирог

2

Excel VBA, 108 байт

Функция анонимного непосредственного окна VBE, которая принимает входные данные в виде массива с разделителями-пробелами из диапазона A1и выводит их в непосредственное окно VBE.

x=Split([A1]):y=UBound(x):For i=0To y-2:?x(i)", ";:Next:If y>0Then?x(i)IIf(y>1,",","")" and "x(i+1)Else?[A1]

1

Ракетка 87

(define(f x)(string-join(map ~a x)", "#:before-last(if(= 2(length x))" and "", and ")))

Я все еще нахожу имена функций в лиспах слишком длинными для игры в гольф.
Seequ

1

Python 62 символа

Предполагая, что я список строк:

(" and", ", and")[len(i) < 2].join(", ".join(i).rsplit(",",1))


1

Юлия (53)

print(join(ARGS,", ",(endof(ARGS)>2?",":"")" and "))

Принимает аргументы из STDIN и выводит в STDOUT

Решение с использованием Base.join (items, delim [, last])

Редактировать:

Контрольные примеры

julia oxfordComma.jl 1

1

julia oxfordComma.jl 1 2

1 и 2

julia oxfordComma.jl 1 2 3

1, 2 и 3


1
Я не знаю Джулию, но похоже, что она не генерирует оксфордскую запятую.
землетрясение

@flornquake печатается с оксфордской запятой из-за необязательного аргумента «последний»
Cruor

2
Должно быть 1, 2, and 3, нет 1, 2 and 3.
штормовое землетрясение

1

Рэнт (108)

[$[l:@b]:[r:each][s:[cmp:[rc];2;[is:different;,]]\s][before:[last:[notfirst:and\s]]][sync:;ordered][arg:b]]

Ungolfed:

[$[l:@b]:
    [r:each]                            # Repeat for each item
    [s:[cmp:[rc];2;[is:different;,]]\s] # Separate by comma if n > 2
    [before:[last:[notfirst:and\s]]]    # Insert "and" before last item
    [sync:;ordered]                     # Force forward order
    [arg:b]                             # Read list
]

Использование:

[$l:{A|B|C|D|E}]

Попробуйте онлайн


1

Пиф, 28

j>", "qlQ2+_t_Q+?"and "tQkeQ

Примеры:

$ pyth programs/oxford.pyth <<< "['1']"
1

$ pyth programs/oxford.pyth <<< "['1','2']"
1 and 2

$ pyth programs/oxford.pyth <<< "['1','2','3']"
1, 2, and 3

Я не могу понять, как вход должен быть отформатирован.
Деннис

@Dennis Я добавлю несколько тестов.
Исаак

Я действительно пробовал это, но это не сработало с моей версией Pyth ( TypeError: can only concatenate list (not "str") to list). Работает нормально с последней версией.
Деннис

@Dennis Да, правило добавления в список было добавлено совсем недавно.
Исаак

1

PHP - 72

Настройте вход:

$i=[1,2,3,4,5];

И тогда процесс:

$x=count($i)-1;if($x) {$i[$x]=" and ".$i[$x];}echo join($x>1?",":"",$i);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.