Generando una sola cadena con una coma de Oxford de una lista

24

¿Cuáles son algunos enfoques inteligentes (breves e idiomáticos) para tomar una lista de cadenas y devolver una sola cadena correctamente puntuada construida a partir de la lista, con cada elemento citado.

Esto se me ocurrió al experimentar con Groovy , para lo cual mi solución demasiado literal pero ilustrativa es

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
}

Es decir, ['1']debería ceder '1', ['1','2']debería ceder '1 and 2', [¿ves lo que hice allí?] Y ['1','2','3']debería ceder '1, 2, and 3'.

Tengo algunas buenas respuestas para Groovy, pero me gustaría ver qué pueden hacer otros idiomas.

¿Cuáles son algunos enfoques inteligentes y compactos en varios idiomas que aprovechan las características y modismos de esos idiomas?

orome
fuente
66
Bienvenido a PPCG. Generalmente las preguntas publicadas aquí son desafíos para la comunidad. Como tales, necesitan un criterio ganador objetivo. Creo que esta pregunta se asigna razonablemente bien a ser un desafío de código de golf . ¿Puedes etiquetarlo como tal? Si es así, creo que debería ajustar un poco las especificaciones de entrada y salida.
Trauma digital
77
Esto sería más interesante con oraciones reales :['we invited the stripper','JFK','Stalin']
ThisSuitIsBlackNot
1
¿Podemos suponer que las cadenas en sí no contienen comas?
Martin Ender
2
El desafío debería haberse titulado "¿Quién da una ---- sobre una coma de Oxford ?"
Igby Largeman
3
@OldCurmudgeon Creo que te refieres a "basura americanizada";)
ThisSuitIsBlackNot

Respuestas:

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 "

CSS no se ve con demasiada frecuencia en el código de golf porque solo puede formatear texto, pero en realidad funciona para este desafío y pensé que sería divertido hacerlo. Véalo en acción usando el fragmento de arriba (haga clic en "Mostrar fragmento de código").

La lista debe estar en un archivo HTML vinculado con cada elemento rodeado de <a>etiquetas y separados por saltos de línea. Los elementos de la lista deben ser los únicos elementos en su elemento principal, por ejemplo

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

Explicación

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

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

Consideremos la versión anterior sin golf. Si no está familiarizado con el funcionamiento de CSS, todo lo que esté fuera de las llaves es un selector que determina el conjunto de elementos HTML a los que se aplican las declaraciones dentro de las llaves. Cada par selector-declaración se llama regla . (Es más complicado que eso, pero será suficiente para esta explicación). Antes de aplicar cualquier estilo, la lista aparece separada solo por espacios.

Queremos agregar comas después de cada palabra, excepto la última, excepto las listas de dos palabras, que no tienen comas. El primer selector, a:not(:last-child):nth-child(n+2):afterselecciona todos los elementos excepto el primero y el último. :nth-child(n+2)es una forma más corta de decir :not(:first-child), y básicamente funciona seleccionando elementos cuyo índice (comenzando en 1) es mayor o igual a 2. (Sí, todavía me confunde un poco. Los documentos MDN podrían ayudar).

Ahora solo tenemos que seleccionar el primer elemento para obtener una coma si hay tres o más elementos en total. a:nth-last-child(n+3):afterfunciona como :nth-child, pero contando desde atrás, por lo que selecciona todos los elementos excepto los dos últimos. La coma toma la unión de los dos conjuntos, y usamos el :after pseudo-elemento para agregar contentinmediatamente después de cada elemento seleccionado.

La segunda regla es más fácil. Necesitamos agregar "y" antes del último elemento en la lista, a menos que sea un solo elemento. En otras palabras, necesitamos seleccionar el último elemento precedido por otro elemento. +es el selector de hermanos adyacentes en CSS.

NinjaOsoMono
fuente
1
¡Brillante! Me encanta. : D
COTO
Si tan solo lo usaras <li>.
slebetman
1
<li>sería ideal, pero eso agregaría dos caracteres adicionales a los selectores. Elegí <a>porque es una letra y no aplica su propio formato.
NinjaBearMonkey
Wow ... esto es muy impresionante. Premio a la inteligencia.
CSS como la respuesta principal? Agradable.
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

Características de Haskell: coincidencia de patrones

usuario3389669
fuente
Puede eliminar algunos espacios
Ray
1
Podrías simplemente eliminarlof[x,y,z]
ver el
Y ahora, es más o menos una recursión estándar. :)
seequ
olvidó la coma y los espacios de Oxford después de las comas, f["a","b","c"]se supone que es, "a, b, and c"pero lo es"a,b and c"
orgulloso Haskeller
44
¿qué tal jugar al golf reemplazando y++", and "++zpor f[y++",",z]:)
orgulloso Haskeller
7

Ruby, 47 bytes

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

Explicación

  • La entrada son los argumentos de la línea de comando ( $*).
  • Cuando $*tiene un tercer elemento ( $*[2]no devuelve nada), tome todos los elementos menos el último y conviértalos en una cadena separada por comas Array#*. Finalmente agregue una coma extra, la cadena " and "y el último argumento de línea de comando.
  • Cuando $*no tiene un tercer elemento, se dieron dos, uno o cero argumentos. Los argumentos se pueden unir de forma segura con la cadena " and "y producir el resultado correcto.
britishtea
fuente
6

Pitón 2 (61)

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

El truco principal es cortar parte de la unión final ", and "para uno y dos elementos. Para uno, todo se corta, y para dos, se elimina la coma. Esto se hace cortando [8-7*len(s):](notando que ses uno más corto después de pop).

Desafortunadamente, dno puede ser reemplazado simplemente con su expresión o popsucedería demasiado tarde.

xnor
fuente
Puede reemplazar el primero scon s=input()y eliminar la primera línea, si no me equivoco. Ahorra 2 caracteres.
tomsmeding
@tomsmeding No entiendo lo que estás sugiriendo. El código se refiere a svarias veces.
xnor
Espera, hago cosas raras esta mañana. Olvídalo. :)
tomsmeding
6

CSS, 62 caracteres 112 caracteres

Inspirado por las otras entradas, incluso más cortas. Tenga en cuenta que requiere que los elementos A no estén separados por espacios en blanco:

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

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

Arreglo para el "uno y dos", como lo señaló 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
fuente
5

Perl (v 5.10+) - 37 35 34 28

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

para ejecutarse perl -ape, con la lista de espacio provisto separado en STDIN.

Salida para varias 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
fuente
Puede reducir esto en 3 bytes cambiando su última sustitución as/(\S+)$/and $1/
ThisSuitIsBlackNot
1
@ThisSuitIsBlackNot Puede soltar el '\ n' (gracias), pero no puede soltar los espacios, o una entrada de "x" se convierte en "y x"
hasta el
Vaya, debería haber probado con más entradas. De todos modos, olvidó el $ancla en su edición, por lo que se foo bar bazconvierte en una entrada de foo, and bar, baz. Además, puede eliminar uno de los espacios haciendos/( \S+)$/ and$1/
ThisSuitIsBlackNot
Mi último comentario todavía le deja a los 35, pero se puede escribir $#F>1como @F>2para cortar una más. Lo siento por todas las sugerencias para micro mejoras, realmente me gusta esta respuesta :)
ThisSuitIsBlackNot
@ThisSuitIsBlackNo es para nada, bienvenido microedits. Estoy compitiendo en secreto con 28 bytes de CJam y 30 bytes de Golfscript. Sin embargo, llegué a 34 sin hacer la traducción de tu comentario antes del último (probado con echo -n ... | wc -c). Si pierdes el espacio antes (\S+)llegarías a 33 caracteres, pero si lo ingresas test thisobtendrás test and this(dos espacios después test), así que sin más Creo que eso está mal.
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

Advertencia: esto modificará el último elemento en una matriz con longitud> 2.

mway
fuente
2
Buen uso de&&
Chris Bloom
4

GNU sed - 69 caracteres incluyendo 1 para -rbandera

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

Toma una lista separada por espacios (bastante idiomática para los scripts de shell).

Ejemplo

$ 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
fuente
Sí, vamos con code-golf .
orome
La segunda salida debería ser en 1 and 2lugar de1, 2
Optimizer
@Optimizer: muy bien. Fijo.
Trauma digital
3

Python 2 - 71, 70 68

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

Recuento de caracteres incluyendo ambos inputy print.

Falko
fuente
3

Ruby, 57 bytes

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

Estoy uniendo la cadena con ,y luego estoy reemplazando la última coma en la cadena con una and(y una coma opcional dependiendo de la longitud de la lista).

Martin Ender
fuente
3

Cobra - 60

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

La List<of T>.joinfunción de Cobra le permite especificar un separador diferente para los dos elementos finales de la lista, que es lo que lo hace tan corto.

Οurous
fuente
3

Perl - 59

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

Une la lista con comas, luego, si la lista tiene más de un elemento, agrega ' and'después de la última coma (si longitud> = 3) o reemplaza la última coma con ella (si longitud == 2).

faubi
fuente
3

PHP, 192 167 146 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)))));

Basado en una función que escribí hace años en http://www.christopherbloom.com/2011/05/21/join-implode-an-array-of-string-values-with-formatting/

Chris Bloom
fuente
¿Puede poner su código (con comas de Oxford) en el texto de respuesta?
Si, lo siento. Estaba en mi teléfono y no pegaba el código correctamente. Actualizaré desde mi escritorio
Chris Bloom
@ Chrisbloom7 incluso si es trivial agregar, la respuesta debe incluir un código de trabajo, no uno que se pueda convertir en uno. Además, debido a que el objetivo es hacer que el código sea lo más pequeño posible, no publicar código de trabajo hace que su puntaje no exista.
orgulloso Haskeller
Disculpas de nuevo. Cartel por primera vez en CG. He arreglado mi respuesta
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; debe llamar al script desde cmdel /vconjunto de interruptores como on, esto es para que no tenga que incluir el extenso setLocal enableDelayedExpansionen el script. De lo contrario, agregue 30 al recuento de bytes y llame al 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
carne sin carne
fuente
3

Groovy, 47 43 57 caracteres, JavaScript ES6 56 caracteres

Maravilloso:

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

Como la matriz está llena de caracteres, podemos reemplazarlos a.size>1pora[1]

JavaScript, ES6:

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

Ambos casos suponen que la variable a tiene la matriz en cuestión.

Optimizador
fuente
66
La coma de Oxford es la coma antes de la conjunción.
Dennis
3

PHP, 86 84 caracteres

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

Con la matriz inicializada, comenzamos a contar:

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

El último elemento de la lista se modifica. Esto debería estar bien porque en PHP, la asignación de matriz y las llamadas a funciones realizan copias.

Damian Yerrick
fuente
3

CJam, 35 30 28 27 bytes

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

Este es un programa que lee desde STDIN e imprime en STDOUT. Pruébalo en línea.

Cómo 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)                               ";

Ejecución de ejemplo

$ 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
fuente
Estoy
detrás de ti
3

Hojas de cálculo de Google, 67 68 67 92 bytes

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

La entrada comienza en la celda A1y continúa hacia abajo sin importar cuántas entradas existan.
JOINlos fusiona a todos en una cadena con un espacio de coma entre cada uno.
FILTERelimina cualquier espacio en blanco para que no termines con comas infinitas al final.
SUBSTITUTEreemplaza la última coma (que se encuentra COUNTAcontando las entradas que no están en blanco).

No es muy emocionante, pero es factible en una sola celda.

Tostadas de ingeniero
fuente
Esto deja de lado la coma de Oxford.
Paraguas
@Umbrella Bueno, eso fue estúpido de mi parte, ¿no? +1 bytes
Engineer Toast
Debería poder quitar el terminal )de esta fórmula para -1 Byte; Bueno dang, esa fue la edición más rápida que he visto +1
Taylor Scott
Dos notas después de probar esto: A:A>""deben convertirse a A:A<>""y esto no implementa omitir la coma de Oxford en casos de solo 2 objetos
Taylor Scott el
@TaylorScott Supongo que solo probé con texto y me perdí el problema >"". La corrección rápida que hice antes claramente no fue probada. No me gusta la dirección que fue ...
Engineer Toast
2

JavaScript (ES6) 60

Suponiendo que no hay cadenas vacías en la matriz de entrada

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

Prueba en la consola FireFox / Firebug

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

Salida

 We invited the stripper, JFK, and Stalin
edc65
fuente
¿Cómo ejecuto esto? Lo probé node --harmonycon una serie de cadenas var a = 'abcdefgh'.split('');, pero simplemente se queda allí con una ...y parece no hacer nada. También +1 por usar la característica exclusiva de ES6 ()=>.
Adrian
@Adrian la función devuelve una cadena, sin ningún resultado. Agregaré una prueba en la respuesta.
edc65
Creo que f=(a,b=a.pop())=>a[0]?a.join(', ')+', and '+b:btambién es correcto y 13 bytes más corto.
Ingo Bürk
1
@ IngoBürk que corta la parte que maneja la coma que tiene 2 elementos. ¿Adivina qué? Con 2 elementos, la salida es incorrecta.
edc65
@ edc65 Ah, oh. Que mal.
Ingo Bürk
2

JavaScript - 60 56 71 67 63

No es exactamente un enfoque idiomático, pero me divertí escribiéndolo y me gusta la expresión regular.

Suponiendo que la matriz se almacena en var a:

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

Acortado simplemente comprobando el índice [2]: (yay boolean coercion)

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

Aparentemente apestaba en las pruebas y salté una prueba de entrada única. Aquí está la versión fija:

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

Afeité 2 caracteres invirtiendo mis booleanos y 3 más moviendo el espacio desde la concatenación al entonces / más del primer ternario:

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

Gracias a @tomsmeding por recordarme que no tengo que forzar a mis booleanos porque JS lo hace por mí. También me di cuenta de que olvidé eliminar los paréntesis que separan el primer ternario de la concatenación dentro de join():

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

¿Lo hice bien? Obligatorio nuevo golfista disculpa.

Adrian
fuente
1
Me gusta regex se conoce como masoquismo.
seequ
En realidad no necesitabas !!nada; a[2]?A:BYa funciona. Puede que tengas que invertir tus booleanos nuevamente .
tomsmeding
@tomsmeding: tienes razón. Siempre desconfío del comportamiento de coerción booleana predeterminado de JavaScript, por lo que tengo el hábito de coaccionar manualmente mis booleanos ...
Adrian
Corrección @Adrian: para cualquier valor que no sea una cadena vacía, a[i]equivale a verdadero . Bueno, casi. Supongo que si obtiene 2 entradas, su matriz tiene una longitud dos. Luego a[2]===undefinedy undefinedevalúa a false. EDIT: tu edición es lo que quería decir :)
tomsmeding
2

Golfscript - 31 39 34 30

¡Hola Mundo! Soy un acechador de mucho tiempo, primer cartel. Aquí está mi solución de 30 bytes en Golfscript (dada la matriz como [1 2 3] ya está en la pila).

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

Los resultados son adecuados para todos los casos de prueba.

EDITAR: ¡Toma eso, CJam!

Josiah Winslow
fuente
Editado para tener en cuenta la matriz de longitud 1.
Josiah Winslow
Editado para un algoritmo más eficiente.
Josiah Winslow
La batalla continúa. : P
Dennis
Ah, pero ahora estamos a mano. : 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," ",", ")

Tenga en cuenta que UBound es uno menos que la longitud de la matriz.

silverpie
fuente
Esto parece agregar la coma cuando hay 2 elementos - no debería
edc65
Buena atrapada. Editado para arreglar.
silverpie
2

Excel VBA, 108 bytes

Función de ventana inmediata anónima de VBE que toma la entrada como matriz delimitada por espacios desde el rango A1y las salidas a la ventana inmediata de 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
fuente
1

Raqueta 87

(define(f x)(string-join(map ~a x)", "#:before-last(if(= 2(length x))" and "", and ")))
Matthew Butterick
fuente
Todavía encuentro que los nombres de las funciones en lisps son demasiado largos para jugar al golf.
seequ
1

Python 62 caracteres

Suponiendo que yo sea la lista de cadenas:

(" and", ", and")[len(i) < 2].join(", ".join(i).rsplit(",",1))
profundo
fuente
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
fuente
1

Julia (53)

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

Toma argumentos de STDIN y sale a STDOUT

Solución usando Base.join (items, delim [, last])

Editar:

Casos de prueba

julia oxfordComma.jl 1

1

julia oxfordComma.jl 1 2

1 y 2

julia oxfordComma.jl 1 2 3

1, 2 y 3

Cruor
fuente
1
No conozco a Julia, pero parece que no genera la coma de Oxford.
flornquake
@flornquake se imprime con la coma de Oxford, debido al argumento opcional "último"
Cruor
2
Debería ser 1, 2, and 3, no 1, 2 and 3.
flornquake
1

Rant (108)

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

Sin golf:

[$[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}]

Pruébalo en línea

Berkin
fuente
1

Pyth, 28

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

Ejemplos:

$ 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
fuente
Parece que no puedo entender cómo se debe formatear la entrada.
Dennis
@ Dennis Agregaré algunos casos de prueba.
isaacg
En realidad lo intenté, pero no funcionó con mi versión de Pyth ( TypeError: can only concatenate list (not "str") to list). Funciona bien con la última versión.
Dennis
@ Dennis Sí, la regla de agregar a la lista se agregó hace poco.
isaacg
1

PHP - 72

Configurar la entrada:

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

Y luego el proceso:

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