Generazione di una singola stringa con una virgola Oxford da un elenco


24

Quali sono alcuni approcci intelligenti (brevi e idiomatici) per prendere un elenco di stringhe e restituire una singola stringa correttamente punteggiata costruita dall'elenco, con ogni elemento citato.

Questo mi è venuto in mente mentre sperimentavo Groovy , per il quale è la mia soluzione troppo letterale, ma illustrativa

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
}

Cioè, ['1']dovrebbe cedere '1', ['1','2']dovrebbe cedere '1 and 2', [vedi cosa ho fatto lì?] E ['1','2','3']dovrebbe cedere '1, 2, and 3'.

Ho delle buone risposte per Groovy, ma mi piacerebbe vedere cosa possono fare altre lingue.

Quali sono alcuni approcci intelligenti compatti in varie lingue che sfruttano le caratteristiche e gli idiomi di quelle lingue?


6
Benvenuti in PPCG. Generalmente le domande pubblicate qui sono sfide per la comunità. Come tali hanno bisogno di un criterio vincente oggettivo. Credo che questa domanda sia ragionevolmente valida per essere una sfida al code-golf . Puoi taggarlo come tale? In tal caso, penso che dovresti restringere un po 'le specifiche di input e output.
Digital Trauma,

7
Ciò sarebbe più interessante con frasi reali :['we invited the stripper','JFK','Stalin']
ThisSuitIsBlackNon

1
Possiamo supporre che le stringhe stesse non contengano già delle virgole?
Martin Ender,

2
La sfida avrebbe dovuto essere intitolata "Chi dà un ---- su una virgola di Oxford ?"
Igby Largeman,

3
@OldCurmudgeon Penso che intendi "spazzatura americanizzata";)
ThisSuitIsBlackNot

Risposte:


76

CSS, 132 116 115 byte

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

CSS non è visto troppo spesso nel golf del codice perché può solo formattare il testo, ma in realtà funziona per questa sfida e ho pensato che sarebbe stato divertente farlo. Guardalo in azione usando lo snippet sopra (fai clic su "Mostra snippet di codice").

L'elenco dovrebbe essere in un file HTML collegato con ogni elemento circondato da <a>tag e separato da interruzioni di riga. Le voci dell'elenco dovrebbero essere gli unici elementi nel loro elemento padre, ad es

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

Spiegazione

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

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

Consideriamo la versione non golfata sopra. Se non hai familiarità con il funzionamento dei CSS, tutto al di fuori delle parentesi graffe è un selettore che determina l'insieme di elementi HTML a cui si applicano le dichiarazioni all'interno delle parentesi graffe. Ogni coppia di selettori-dichiarazioni è chiamata regola . (È più complicato di così, ma basterà per questa spiegazione.) Prima di applicare qualsiasi stile, l'elenco appare separato da soli spazi.

Vogliamo aggiungere virgole dopo ogni parola tranne l'ultima, ad eccezione degli elenchi di due parole, che non ottengono virgole. Il primo selettore, a:not(:last-child):nth-child(n+2):afterseleziona tutti gli elementi tranne il primo e l'ultimo. :nth-child(n+2)è un modo più breve per dirlo :not(:first-child), e funziona fondamentalmente selezionando elementi il ​​cui indice (a partire da 1) è maggiore o uguale a 2. (Sì, mi confonde ancora un po '. I documenti MDN potrebbero aiutare.)

Ora dobbiamo solo selezionare il primo elemento per ottenere una virgola se ci sono tre o più elementi in totale. a:nth-last-child(n+3):afterfunziona come :nth-child, ma contando da dietro, quindi seleziona tutti gli elementi tranne gli ultimi due. La virgola prende l'unione dei due set e usiamo lo :after pseudo-elemento per aggiungere contentimmediatamente dopo ogni elemento selezionato.

La seconda regola è più semplice. Dobbiamo aggiungere "e" prima dell'ultimo elemento nell'elenco, a meno che non sia un singolo elemento. In altre parole, dobbiamo selezionare l'ultimo elemento preceduto da un altro elemento. +è il selettore di fratelli adiacenti nel CSS.


1
Brillante! Lo adoro. : D
COTO

Se solo tu usassi <li>.
Slebetman,

1
<li>sarebbe l'ideale, ma ciò aggiungerebbe due caratteri extra ai selettori. Ho scelto <a>perché è una lettera e non applica la propria formattazione.
NinjaBearMonkey,

Caspita ... è davvero impressionante. Premio di intelligenza.

CSS come risposta principale? Bello.
Brandon,

13

Haskell: 81, 77 74 caratteri

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

Caratteristiche di Haskell: corrispondenza dei motivi


È possibile rimuovere alcuni spazi
Ray

1
Potresti semplicemente rimuoveref[x,y,z]
vedi il

E ora, è praticamente una ricorsione standard. :)
seequ,

hai dimenticato la virgola oxford e gli spazi dopo le virgole - f["a","b","c"]dovrebbe essere "a, b, and c"ma è"a,b and c"
orgoglioso haskeller il

4
che ne dici di giocare a golf sostituendolo y++", and "++zcon f[y++",",z]:)
orgoglioso haskeller il

7

Rubino, 47 byte

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

Spiegazione

  • L'input sono gli argomenti della riga di comando ( $*).
  • Quando $*ha un terzo elemento ( $*[2]non restituisce zero), prendi tutti gli elementi meno l'ultimo e trasformali in una stringa separata da virgola usando Array#*. Infine aggiungi una virgola aggiuntiva, la stringa " and "e l'ultimo argomento della riga di comando.
  • Quando $*non ha un terzo elemento, sono stati forniti due, uno o zero argomenti. Gli argomenti possono essere uniti in modo sicuro con la stringa " and "e produrre il risultato corretto.

6

Python 2 (61)

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

Il trucco principale è tagliare parte del falegname finale ", and "per uno e due elementi. Per uno, tutto viene tagliato e per due, la virgola viene rimossa. Questo viene fatto tagliando [8-7*len(s):](notando che sè più corto dopo pop).

Sfortunatamente, dnon può essere semplicemente sostituito con la sua espressione o popaccadrebbe troppo tardi.


Puoi sostituire la prima scon s=input()e rimuovere la prima riga, se non sbaglio. Salva 2 caratteri.
Tomsmeding,

@tomsmeding Non capisco cosa stai suggerendo. Il codice si riferisce a spiù volte.
xnor

Aspetta, faccio cose strane stamattina. Dimenticalo. :)
tomsmeding,

6

CSS, 62 caratteri 112 caratteri

Ispirato dalle altre voci, anche più breve. Si noti che richiede che gli elementi A non siano separati da spazi bianchi:

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

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

Correzione per "uno e due", come indicato da 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/


5

Perl (v 5.10+) - 37 35 34 28

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

con cui eseguire perl -ape, con l'elenco dello spazio fornito separato STDIN.

Uscita per vari ingressi:

$ 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

Puoi ridurlo di 3 byte modificando la tua ultima sostituzione ins/(\S+)$/and $1/
ThisSuitIsBlackNon

1
@ThisSuitIsBlackNot Puoi rilasciare '\ n' (grazie), ma non puoi rilasciare gli spazi, oppure un input di "x" diventa "e x"
circa

Oops, avrebbe dovuto testare con più input. Ad ogni modo, hai dimenticato l' $ancora nella modifica, quindi un input di foo bar bazdiventa foo, and bar, baz. Inoltre, puoi rimuovere uno degli spazi facendos/( \S+)$/ and$1/
ThisSuitIsBlackNot

Il mio ultimo commento che si lascia ancora a 35, ma si può scrivere $#F>1come @F>2a radersi uno di più. Ci scusiamo per tutti i suggerimenti per i micro miglioramenti, mi piace davvero questa risposta :)
ThisSuitIsBlackNon

@ThisSuitIsBlackNon affatto - microedits benvenuti. Sto gareggiando segretamente con 28 byte di CJam e 30 byte di Golfscript. Comunque sono arrivato a 34 senza aver fatto la traduzione del tuo commento prima dell'ultimo (testato con echo -n ... | wc -c). Se perdi lo spazio prima (\S+)di arrivare a 33 caratteri, ma se lo inserirai test thisotterrai test and this(due spazi dopo test), quindi senza più Penso che sia sbagliato.
circa

4

Javascript (63)

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

casi:

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

Avvertenza: questo modificherà l'ultimo elemento in un array con lunghezza> 2.


2
Buon uso di&&
Chris Bloom,

4

GNU sed - 69 caratteri di cui 1 per -rbandiera

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

Prende un elenco separato da spazi (abbastanza idiomatico per gli script di shell).

Esempio

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

Sì, andiamo con code-golf .
orome,

La seconda uscita dovrebbe essere 1 and 2invece di1, 2
Ottimizzatore

@Optimizer - assolutamente giusto. Fisso.
Trauma digitale

3

Python 2 - 71, 70 68

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

Conteggio dei personaggi inclusi entrambi inpute print.


3

Rubino, 57 byte

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

Sto unendo la stringa con ,e quindi sto sostituendo l'ultima virgola nella stringa con una and(e virgola facoltativa a seconda della lunghezza dell'elenco).


3

Cobra - 60

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

La List<of T>.joinfunzione di Cobra ti consente di specificare un separatore diverso per gli ultimi due elementi dell'elenco, che è ciò che lo rende così breve.


3

Perl - 59

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

Unisce l'elenco con le virgole, quindi se l'elenco ha più di un elemento, aggiunge ' and'dopo l'ultima virgola (se lunghezza> = 3) o sostituisce con essa l'ultima virgola (se lunghezza == 2).


3

PHP, 192 167 146 136 caratteri:

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

Basato su una funzione che ho scritto anni fa su http://www.christopherbloom.com/2011/05/21/join-implode-an-array-of-string-values-with-formatting/


Puoi per favore inserire il tuo codice (con le virgole di Oxford) nel testo della risposta?

Sì scusa. Ero sul mio telefono e non avrei incollato correttamente il codice. Aggiornerò dal mio desktop
Chris Bloom il

@ Chrisbloom7 anche se è banale aggiungere, la risposta dovrebbe includere un codice funzionante, non uno che può essere fatto per essere uno. inoltre, poiché l'obiettivo è rendere il codice il più piccolo possibile, non pubblicare il codice di lavoro rende il tuo punteggio inesistente.
orgoglioso haskeller il

Ci scusiamo ancora. Poster per la prima volta in CG. Ho risolto la mia risposta
Chris Bloom,

3

Lotto - 151 byte

@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; devi chiamare lo script da cmdcon l' /vinterruttore impostato come on, questo è quindi non devo includere il lungo setLocal enableDelayedExpansionnello script. Altrimenti aggiungi 30 al conteggio dei byte e chiama lo 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

3

Groovy, 47 43 57 caratteri, JavaScript ES6 56 caratteri

Groovy:

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

Poiché l'array è pieno di caratteri, possiamo sostituirlo a.size>1cona[1]

JavaScript, ES6:

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

Entrambi i casi presuppongono che la variabile a abbia l'array in questione.


6
La virgola Oxford è la virgola prima della congiunzione.
Dennis,

3

PHP, 86 84 caratteri

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

Con l'array inizializzato, iniziamo a contare:

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

L'ultimo elemento nell'elenco è stato modificato. Questo dovrebbe essere OK perché in PHP, l'assegnazione di array e le chiamate di funzione eseguono copie.


3

CJam, 35 30 28 27 byte

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

Questo è un programma che legge da STDIN e stampa su STDOUT. Provalo online.

Come funziona

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

Esempio di esecuzione

$ 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

Sono sulla tua coda - vedi sotto :-)
circa

3

Fogli Google, 67 68 67 92 byte

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

L'input inizia dalla cella A1e continua verso il basso per quanto esistono molte voci.
JOINli unisce tutti in una stringa con uno spazio virgola tra ciascuno.
FILTERrimuove eventuali spazi vuoti in modo da non finire con virgole infinite alla fine.
SUBSTITUTEsostituisce l'ultima virgola (trovata COUNTAcontando gli input non vuoti).

Non è molto eccitante ma è fattibile in una singola cella.


Questo esclude la virgola oxford.
Ombrello

@Umbrella Beh, è ​​stato stupido da parte mia, vero? +1 byte
Ingegnere Toast,

Dovresti essere in grado di eliminare il terminale )da questa formula per -1 byte; Beh, dang, è stata la modifica più rapida che abbia mai visto +1
Taylor Scott,

Due note dopo averlo testato - A:A>""dovrebbero essere convertite in A:A<>""e questo non implementa l'omissione della virgola oxford su casi di solo 2 oggetti
Taylor Scott

@TaylorScott I guess I only ever tested with text and missed the issue with >"". The quickie correction I made earlier clearly wasn't tested. I don't like the direction that went...
Engineer Toast

2

JavaScript (ES6) 60

Assuming no empty strings in input array

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

Test In FireFox/Firebug console

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

Output

 We invited the stripper, JFK, and Stalin

Come eseguo questo? L'ho provato node --harmonycon una serie di stringhe var a = 'abcdefgh'.split('');, ma si trova lì con un ...e sembra non fare nulla. Inoltre +1 per l'utilizzo della funzione ES6 unica ()=>.
Adrian,

@Adrian la funzione restituisce una stringa, senza alcun output. Aggiungerò un test nella risposta.
edc65,

Penso che f=(a,b=a.pop())=>a[0]?a.join(', ')+', and '+b:bsia anche corretto e più corto di 13 byte.
Ingo Bürk,

1
@ IngoBürk che taglia la parte che gestisce la virgola con 2 elementi. Indovina un po? Con 2 elementi l'output è errato.
edc65,

@ edc65 Ah, d'oh. Può essere cattivo.
Ingo Bürk,

2

JavaScript - 60 56 71 67 63

Non esattamente un approccio idiomatico, ma mi sono divertito a scriverlo e mi piace regex.

Supponendo che l'array sia archiviato in var a:

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

Accorciato semplicemente controllando l'indice [2]: (yay coercizione booleana)

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

Apparentemente faccio schifo durante i test e ho saltato un test a voce singola. Ecco la versione fissa:

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

Ho eliminato 2 caratteri invertendo i miei booleani e altri 3 spostando lo spazio dalla concatenazione all'allora / altrimenti del primo ternario:

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

Thanks to @tomsmeding for reminding me that I don't have to coerce my booleans because JS does that for me. I also realised I forgot to remove the parentheses separating the first ternary from the concatenation inside the join():

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

Did I do that right? Obligatory new golfer apology.


1
Liking regex is known as masochism.
seequ

Actually you didn't need the !! at all; a[2]?A:B works already. Might have to invert your booleans again.
tomsmeding

@tomsmeding -- you are correct. I am always wary of JavaScript's default boolean coercion behaviour, so I have the borne-in habit of manually coercing my booleans...
Adrian

@Adrian Correction: for any value but an empty string, a[i] equates to true. Well, almost. I'm assuming that if you get 2 inputs, your array has length two. Then a[2]===undefined and undefined evaluates to false. EDIT: your edit is what I meant :)
tomsmeding

2

Golfscript - 31 39 34 30

Hello, World! I am a long time lurker, first time poster. Here is my 30 byte solution in Golfscript (given the array such as [1 2 3] is already on the stack).

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

The results are proper for all test cases.

EDIT: Take that, CJam!


Edited to account for the length 1 array.
Josiah Winslow

Edited for a more efficient algorithm.
Josiah Winslow

The battle continues. :P
Dennis

Ah, but now we're even. :P
Josiah Winslow

2

Xojo, 52 71 83 chars

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

Note that UBound is one less than the array length.


This seems to add the comma when there are 2 items - should not
edc65

Good catch. Edited to fix.
silverpie

2

Excel VBA, 108 Bytes

Anonymous VBE immediate window function that takes input as space delimited array from range A1 and outputs to the VBE immediate window.

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

Racket 87

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

I still find the function names in lisps to be way too long for golfing.
seequ

1

Python 62 chars

Assuming that i is the list of strings:

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

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;

1

Julia (53)

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

Takes args from STDIN and outputs to STDOUT

Solution using Base.join(items, delim[, last])

Edit:

Test cases

julia oxfordComma.jl 1

1

julia oxfordComma.jl 1 2

1 and 2

julia oxfordComma.jl 1 2 3

1, 2, and 3


1
I don't know Julia, but this looks like it doesn't generate the Oxford comma.
flornquake

@flornquake it does print with the Oxford comma, due to the optional "last" argument
Cruor

2
It should be 1, 2, and 3, not 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]]

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
]

Usage:

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

Try it online


1

Pyth, 28

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

Examples:

$ 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

I can't seem to figure out how the input should be formatted.
Dennis

@Dennis I'll add some test cases.
isaacg

I had actually tried that, but it didn't work with my version of Pyth (TypeError: can only concatenate list (not "str") to list). It works fine with the latest version.
Dennis

@Dennis Yeah, the adding to list rule was added quite recently.
isaacg

1

PHP - 72

Set up the input:

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

And then the process:

$x=count($i)-1;if($x) {$i[$x]=" and ".$i[$x];}echo join($x>1?",":"",$i);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.