Gerando uma única sequência com uma vírgula Oxford a partir de uma lista

24

Quais são algumas abordagens inteligentes (breves e idiomáticas) para obter uma lista de cadeias e retornar uma única cadeia adequadamente pontuada criada a partir da lista, com cada elemento citado.

Isso surgiu para mim ao experimentar o Groovy , para o qual minha solução muito literal, mas ilustrativa, é

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
}

Ou seja, ['1']deve ceder '1', ['1','2']deve ceder '1 and 2'[veja o que eu fiz lá?] E ['1','2','3']deve ceder '1, 2, and 3'.

Tenho boas respostas para o Groovy, mas gostaria de ver o que outros idiomas podem fazer.

Quais são algumas abordagens inteligentes e compactas em vários idiomas que tiram proveito dos recursos e idiomas desses idiomas?

orome
fonte
6
Bem-vindo ao PPCG. Geralmente as perguntas postadas aqui são desafios para a comunidade. Como tal, eles precisam de um critério de vitória objetivo. Acredito que essa pergunta mapeia razoavelmente bem o desafio do código-golfe . Você pode marcá-lo como tal? Nesse caso, acho que você deve reforçar um pouco as especificações de entrada e saída.
Digital Trauma
7
Isso seria mais interessante com frases reais :['we invited the stripper','JFK','Stalin']
ThisSuitIsBlackNot
11
Podemos supor que as próprias seqüências de caracteres não contenham vírgulas?
Martin Ender
2
O desafio deveria ter sido intitulado "Quem dá uma ---- sobre uma vírgula de Oxford ?"
Igby Largeman
3
@OldCurmudgeon Acho que você quer dizer "lixo americanizado";)
ThisSuitIsBlackNot

Respostas:

76

CSS, 132 116 115 bytes

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

O CSS não é visto com muita frequência no código golf porque ele pode apenas formatar texto, mas na verdade funciona para esse desafio e achei que seria divertido fazer isso. Veja-o em ação usando o snippet acima (clique em "Mostrar snippet de código").

A lista deve estar em um arquivo HTML vinculado com cada elemento cercado por <a>tags e separado por quebras de linha. Os itens da lista devem ser os únicos elementos em seu elemento pai, por exemplo

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

Explicação

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

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

Vamos considerar a versão não destruída acima. Se você não estiver familiarizado com o funcionamento do CSS, tudo fora dos chavetas é um seletor que determina o conjunto de elementos HTML aos quais as declarações dentro dos chavetas se aplicam. Cada par de seletor-declaração é chamado de regra . (É mais complicado que isso, mas será suficiente para esta explicação.) Antes de aplicar qualquer estilo, a lista aparece separada apenas por espaços.

Queremos adicionar vírgulas após cada palavra, exceto a última, exceto as listas de duas palavras, que não possuem vírgulas. O primeiro seletor,, a:not(:last-child):nth-child(n+2):afterseleciona todos os elementos, exceto o primeiro e o último. :nth-child(n+2)é uma maneira mais curta de dizer :not(:first-child), e basicamente funciona selecionando elementos cujo índice (começando em 1) é maior ou igual a 2. (Sim, ainda me confunde um pouco. Os documentos do MDN podem ajudar.)

Agora só precisamos selecionar o primeiro elemento para obter uma vírgula se houver três ou mais elementos no total. a:nth-last-child(n+3):afterfunciona como :nth-child, mas contando pela parte de trás, por isso seleciona todos os elementos, exceto os dois últimos. A vírgula aceita a união dos dois conjuntos e usamos o :after pseudoelemento para adicionar contentimediatamente após cada elemento selecionado.

A segunda regra é mais fácil. Precisamos adicionar "e" antes do último elemento da lista, a menos que seja um único elemento. Em outras palavras, precisamos selecionar o último elemento que é precedido por outro elemento. +é o seletor de irmão adjacente em CSS.

NinjaBearMonkey
fonte
11
Brilhante! Eu amo isso. : D
COTO
Se você usasse <li>.
slebetman 12/09
11
<li>seria ideal, mas isso adicionaria dois caracteres extras aos seletores. Eu escolhi <a>porque é uma letra e não aplica sua própria formatação.
NinjaBearMonkey
Uau ... isso é muito impressionante. Prêmio Cleverness.
CSS como a melhor resposta? Agradável.
Brandon
13

Haskell: 81, 77 74 caracteres

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

Recursos de Haskell: Correspondência de padrões

user3389669
fonte
Você pode remover alguns espaços
Ray
11
Você pode simplesmente removerf[x,y,z]
Veja
E agora, é praticamente uma recursão padrão. :)
12/09/14
você esqueceu a vírgula de Oxford e espaços após as vírgulas - f["a","b","c"]é suposto ser "a, b, and c", mas é"a,b and c"
haskeller orgulhoso
4
como sobre golfe, substituindo y++", and "++zpor f[y++",",z]:)
haskeller orgulhoso
7

Ruby, 47 bytes

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

Explicação

  • A entrada são os argumentos da linha de comando ( $*).
  • Quando $*tiver um terceiro elemento ( $*[2]não retorna nulo), pegue todos os elementos menos o último e transforme-os em uma String separada por vírgula usando Array#*. Por fim, adicione uma vírgula extra, a string " and "e o último argumento da linha de comando.
  • Quando $*não tem terceiro elemento, dois, um ou zero argumentos foram dados. Os argumentos podem ser unidos com segurança à string " and "e produzir o resultado correto.
britishtea
fonte
6

Python 2 (61)

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

O principal truque é cortar parte do marceneiro final ", and "para um e dois elementos. Por um lado, tudo é cortado e, por dois, a vírgula é removida. Isso é feito cortando [8-7*len(s):](observando que sé um menor após o pop).

Infelizmente, dnão pode ser substituído apenas por sua expressão ou popisso aconteceria tarde demais.

xnor
fonte
Você pode substituir o primeiro spor s=input()e remover a primeira linha, se não me engano. Salva 2 caracteres.
tomsmeding 13/09/14
@tomsmeding Não entendo o que você está sugerindo. O código está se referindo a svárias vezes.
xnor
Espere, eu faço coisas estranhas esta manhã. Esqueça isso. :)
tomsmeding 13/09/14
6

CSS, 62 caracteres 112 caracteres

Inspirado pelas outras entradas, ainda mais curtas. Observe que isso requer que os elementos A não sejam separados por espaços em branco:

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

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

Correção para o "um e dois", como apontado por Dennis:

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/

olvlvl
fonte
5

Perl (v 5.10+) - 37 35 34 28

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

para ser executado perl -ape, com a lista fornecida com o espaço separado STDIN.

Saída para várias entradas:

$ 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
abligh
fonte
Você pode reduzir isso em 3 bytes, alterando sua última substituição paras/(\S+)$/and $1/
ThisSuitIsBlackNot
11
@ThisSuitIsBlackNot Você pode soltar o '\ n' (obrigado), mas não pode soltar os espaços, ou uma entrada de "x" se torna "e x"
18/09/14
Opa, deveria ter testado com mais entradas. De qualquer forma, você esqueceu a $âncora em sua edição, portanto, uma entrada de foo bar baztorna-se foo, and bar, baz. Além disso, você pode remover um dos espaços, fazendos/( \S+)$/ and$1/
ThisSuitIsBlackNot
Meu último comentário ainda deixa você em 35, mas você pode escrever $#F>1como @F>2para raspar mais uma. Desculpe por todas as sugestões de melhorias micro, eu realmente gosto desta resposta :)
ThisSuitIsBlackNot
@ThisSuitIsBlackNão é nada - microdits são bem-vindos. Estou competindo secretamente com 28 bytes de CJam e 30 bytes de Golfscript. No entanto, cheguei ao 34 sem fazer a tradução do seu comentário antes da última (testado com echo -n ... | wc -c). Se você perder o espaço antes, (\S+)você chegaria a 33 caracteres, mas se você colocar test this, receberá test and this(dois espaços depois test)), sem mais Eu acho que está errado.
abligh
4

Javascript (63)

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

Casos:

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

Advertência: isso modificará o último elemento em uma matriz com comprimento> 2.

mway
fonte
2
Bom uso de&&
Chris Bloom
4

GNU sed - 69 caracteres, incluindo 1 para a -rbandeira

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

Leva uma lista separada por espaço (bastante idiomática para scripts de shell).

Exemplo

$ 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
$
Trauma Digital
fonte
Sim, vamos com código-golfe .
orome 11/09/14
A segunda saída deve ser em 1 and 2vez de1, 2
Optimizer
@ Optimizer - muito bem. Fixo.
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]

Contagem de caracteres, incluindo ambos, inpute print.

Falko
fonte
3

Ruby, 57 bytes

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

Estou ingressando na string ,e, em seguida, substituindo a última vírgula na string por uma and(e opcional, dependendo do tamanho da lista).

Martin Ender
fonte
3

Cobra - 60

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

A List<of T>.joinfunção Cobra permite especificar um separador diferente para os dois elementos finais da lista, que é o que torna isso tão curto.

Furioso
fonte
3

Perl - 59

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

Une a lista com vírgulas e, se a lista tiver mais de um elemento, adiciona ' and'após a última vírgula (se comprimento> = 3) ou substitui a última vírgula por ela (se comprimento == 2).

faubi
fonte
3

PHP, 192 167 146 136 136 caracteres:

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

Com base em uma função que escrevi anos atrás em http://www.christopherbloom.com/2011/05/21/join-implode-an-array-of-string-values-with-formatting/

Chris Bloom
fonte
Você pode colocar seu código (com vírgulas Oxford) no texto da resposta?
Sim, desculpe. Eu estava no meu telefone e ele não colava o código corretamente. Vou atualizar da minha área de trabalho
Chris Bloom
@ Chrisbloom7, mesmo que seja trivial adicionar, a resposta deve incluir um código de trabalho, e não um que possa ser feito para ser um. Além disso, como o objetivo é tornar o código o menor possível, a não publicação do código ativo torna sua pontuação inexistente.
proud haskeller
Desculpas novamente. Pôster da primeira vez na CG. Eu fixo minha resposta
Chris Bloom
3

Lote - 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!

Nota; você precisa chamar o script cmdcom a /vopção definida como on, para não precisar incluir o longo setLocal enableDelayedExpansionno script. Caso contrário, adicione 30 à contagem de bytes e chame o script normalmente.

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
desgrudar
fonte
3

Groovy, 47 43 57 caracteres, JavaScript ES6 56 caracteres

Groovy:

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

Como a matriz é preenchida com caracteres, podemos substituir a.size>1pora[1]

JavaScript, ES6:

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

Ambos os casos assumem que a variável a tem a matriz em questão.

Optimizer
fonte
6
A vírgula de Oxford é a vírgula antes da conjunção.
Dennis
3

PHP, 86 84 caracteres

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

Com a matriz inicializada, começamos a contar:

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

Pré-especificado:

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

O último item da lista é modificado. Isso deve ser bom, porque no PHP, a atribuição de matriz e as chamadas de função executam cópias.

Damian Yerrick
fonte
3

CJam, 35 30 28 27 bytes

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

Este é um programa que lê de STDIN e imprime em STDOUT. Experimente online.

Como funciona

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

Exemplo de execução

$ 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
Dennis
fonte
Estou no seu
encalço
3

Planilhas Google, 67 68 67 92 bytes

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

A entrada começa na célula A1e continua inativa por várias entradas.
JOINmescla todos eles em uma cadeia de caracteres com um espaço de vírgula entre cada um.
FILTERremove todos os espaços em branco para que você não termine com vírgulas infinitas no final.
SUBSTITUTEsubstitui a última vírgula (encontrada COUNTAcontando as entradas que não estão em branco).

Não é muito emocionante, mas é factível em uma única célula.

Engenheiro Toast
fonte
Isso deixa de fora a vírgula oxford.
Umbrella
@ Guarda-chuva Bem, isso foi estúpido da minha parte, não foi? +1 bytes
Toast do engenheiro
Você poderá deixar o terminal )dessa fórmula para -1 Byte; Bem, caramba, essa foi a edição mais rápida que eu já vi +1
Taylor Scott
Duas notas Depois de testar esta - A:A>""devem ser convertidos para A:A<>""e este não implementa omitindo a vírgula oxford sobre casos de apenas 2 objetos
Taylor Scott
@TaylorScott Acho que só testei com texto e perdi o problema >"". A correção rápida que fiz anteriormente claramente não foi testada. Eu não gosto da direção que foi ...
Engineer Toast
2

JavaScript (ES6) 60

Supondo que não haja cadeias vazias na matriz de entrada

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

Teste no console do FireFox / Firebug

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

Saída

 We invited the stripper, JFK, and Stalin
edc65
fonte
Como eu executo isso? Eu tentei node --harmonycom uma série de strings var a = 'abcdefgh'.split('');, mas ele fica lá com um ...e parece não fazer nada. Também +1 para usar o recurso exclusivo do ES6 ()=>.
Adrian
@Adrian a função retorna uma string, sem nenhuma saída. Vou adicionar um teste na resposta.
Edc65
Eu acho que f=(a,b=a.pop())=>a[0]?a.join(', ')+', and '+b:btambém está correto e 13 bytes mais curto.
Ingo Bürk
11
@ IngoBürk que corta a parte que lida com a vírgula com 2 itens. Adivinha? Com 2 itens, a saída está errada.
edc65 12/09
@ edc65 Ah, ah! Pode ser ruim.
Ingo Bürk
2

JavaScript - 60 56 71 67 63

Não é exatamente uma abordagem idiomática, mas me diverti escrevendo e gosto de regex.

Supondo que a matriz esteja armazenada em var a:

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

Encurtado simplesmente verificando o índice [2]: (yay coerção booleana)

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

Aparentemente, sou péssimo nos testes e pulei um teste de entrada única. Aqui está a versão fixa:

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

Raspei 2 caracteres invertendo meus booleanos e mais 3 movendo o espaço da concatenação para o então / outro do primeiro ternário:

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

Obrigado a @tomsmeding por me lembrar que não tenho que coagir meus booleanos porque JS faz isso por mim. Também percebi que esqueci de remover os parênteses que separam o primeiro ternário da concatenação dentro do join():

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

Eu fiz isso certo? Novo pedido de desculpas obrigatório.

Adrian
fonte
11
Gostar de regex é conhecido como masoquismo.
12/09/14
Na verdade, você não precisava disso !!; a[2]?A:Bjá funciona. Pode ter que inverter seus booleanos novamente .
tomsmeding 13/09/14
@tomsmeding - você está correto. Estou sempre desconfiar de comportamento coerção boolean padrão de JavaScript, por isso tenho o borne-in hábito de coagir manualmente meus booleans ...
Adrian
Correção @Adrian: para qualquer valor, exceto uma string vazia, a[i]equivale a true . Bem, quase. Estou assumindo que, se você receber 2 entradas, sua matriz terá o comprimento dois. Então a[2]===undefinede undefinedavalia como false. EDIT: sua edição é o que eu quis dizer :)
tomsmeding
2

Golfscript - 31 39 34 30

Olá Mundo! Sou muito espreita, pôster da primeira vez. Aqui está minha solução de 30 bytes no Golfscript (dado o array como [1 2 3] já está na pilha).

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

Os resultados são adequados para todos os casos de teste.

EDIT: Pegue isso, CJam!

Josiah Winslow
fonte
Editado para contabilizar o comprimento 1 da matriz.
Josiah Winslow
Editado para um algoritmo mais eficiente.
Josiah Winslow
A batalha continua. : P
Dennis
Ah, mas agora estamos quites. : P
Josiah Winslow
2

Xojo, 52 71 83 caracteres

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

Observe que UBound é um a menos que o comprimento da matriz.

silverpie
fonte
Este parece adicionar a vírgula quando há 2 itens - não deve
edc65
Boa pegada. Editado para corrigir.
silverpie
2

Excel VBA, 108 bytes

Função de janela imediata VBE anônima que recebe entrada como matriz delimitada por espaço do intervalo A1e sai para a janela imediata 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]
Taylor Scott
fonte
1

Raquete 87

(define(f x)(string-join(map ~a x)", "#:before-last(if(= 2(length x))" and "", and ")))
Matthew Butterick
fonte
Ainda acho que os nomes das funções em lisps são muito longos para o golfe.
seequ
1

Python 62 chars

Supondo que i seja a lista de strings:

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

C # 102 Chars

var i=m.Count(),l=i;if(l>2)l--;var r=string.Join(", ",m.Take(l));if(l!=i)r+=" and "+m.Last();return r;
Stephan Schinkel
fonte
1

Julia (53)

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

Leva args de STDIN e sai para STDOUT

Solução usando Base.join (itens, delim [, último])

Editar:

Casos de teste

julia oxfordComma.jl 1

1 1

julia oxfordComma.jl 1 2

1 e 2

julia oxfordComma.jl 1 2 3

1, 2 e 3

Cruor
fonte
11
Não conheço Julia, mas parece que não gera a vírgula de Oxford.
flornquake
@flornquake ele faz impressão com a vírgula Oxford, devido ao "último" argumento opcional
Cruor
2
Deveria ser 1, 2, and 3, não 1, 2 and 3.
Flornquake 18/09/14
1

Rant (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
]

Uso:

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

Experimente online

Berkin
fonte
1

Pyth, 28

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

Exemplos:

$ 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
isaacg
fonte
Não consigo descobrir como a entrada deve ser formatada.
Dennis
@Dennis Vou adicionar alguns casos de teste.
Isaacg
Na verdade, eu tentei isso, mas não funcionou com a minha versão do Pyth ( TypeError: can only concatenate list (not "str") to list). Funciona bem com a versão mais recente.
Dennis
@ Dennis Sim, a regra de adicionar à lista foi adicionada recentemente.
Isaacg #
1

PHP - 72

Configure a entrada:

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

E então o processo:

$x=count($i)-1;if($x) {$i[$x]=" and ".$i[$x];}echo join($x>1?",":"",$i);
apatia-
fonte