# Generating a single string with an Oxford comma from a list

What are some clever (brief and idiomatic) approaches to taking a list of strings and returning a single properly punctuated string built from the list, with each element quoted.

This came up for me while experimenting with Groovy, for which my too-literal, but illustrative solution is

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 }  That is, ['1'] should yield '1', ['1','2'] should yield '1 and 2', [see what I did there?] and ['1','2','3'] should yield '1, 2, and 3'. I have some good answers for Groovy, but I'd like to see what other languages can do. What are some compact clever approaches in various languages that take advantage of the features and idioms of those languages? • Welcome to PPCG. Generally questions posted here are challenges to the community. As such they need an objective winning criteria. I believe this question maps reasonably well to being a code-golf challenge. Can you tag it as such? If so, I think you should tighten up the input and output specifications a bit. – Digital Trauma Sep 11 '14 at 18:58 • This would be more interesting with real sentences: ['we invited the stripper','JFK','Stalin'] – ThisSuitIsBlackNot Sep 11 '14 at 21:06 • Can we assume that the strings themselves don't contain commas already? – Martin Ender Sep 11 '14 at 21:16 • Challenge should have been titled "Who gives a ---- about an Oxford comma?" – Igby Largeman Sep 12 '14 at 7:14 • @OldCurmudgeon I think you mean "Americanized rubbish" ;) – ThisSuitIsBlackNot Sep 21 '14 at 18:59 ## 42 Answers # CSS, 132116 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 " <p> <a>one</a> </p> <p> <a>one</a> <a>two</a> </p> <p> <a>one</a> <a>two</a> <a>three</a> </p> <p> <a>one</a> <a>two</a> <a>three</a> <a>four</a> </p> a:not(:last-child):nth-child(n+2):after,a:nth-last-child(n+3):after{content:","}a+:last-child:before{content:"and "  CSS is not seen too often in code golf because it can only format text, but it actually works for this challenge and I thought it would be fun to do. See it in action using the snippet above (click "Show code snippet"). List should be in a linked HTML file with each element surrounded by <a> tags and separated by line breaks. The list items should be the only elements in their parent element, e.g. <a>one</a> <a>two</a> <a>three</a>  ## Explanation a:not(:last-child):nth-child(n+2)::after, a:nth-last-child(n+3)::after { content: ","; } a + :last-child::before { content: "and "; }  Let's consider the ungolfed version above. If you're not familiar with how CSS works, everything outside the curly braces is a selector that determines the set of HTML elements to which the declarations inside the braces apply. Each selector-declaration pair is called a rule. (It's more complicated than that but will suffice for this explanation.) Before any styling is applied, the list appears separated by only spaces. We want to add commas after every word except the last, except for two-word lists, which get no commas. The first selector, a:not(:last-child):nth-child(n+2):after, selects all elements except the first and the last. :nth-child(n+2) is a shorter way of saying :not(:first-child), and it basically works by selecting elements whose index (starting at 1) is greater than or equal to 2. (Yes, it still confuses me a little. The MDN docs might help.) Now we just need to select the first element to get a comma if there are three or more elements total. a:nth-last-child(n+3):after works like :nth-child, but counting from the back, so it selects all elements except the last two. The comma takes the union of the two sets, and we use the :after pseudo-element to add content immediately after each selected element. The second rule is easier. We need to add "and" before the last element in the list, unless it is a single element. In other words, we need to select the last element that is preceded by another element. + is the adjacent sibling selector in CSS. • Brilliant! I love it. :D – COTO Sep 11 '14 at 22:28 • If only you'd use <li>. – slebetman Sep 12 '14 at 4:01 • <li> would be ideal, but that would add two extra characters to the selectors. I chose <a> because it is one letter and doesn't apply its own formatting. – NinjaBearMonkey Sep 12 '14 at 4:07 • Wow... this is very impressive. Cleverness award. – user16402 Sep 12 '14 at 18:38 • CSS as the top answer? Nice. – Brandon Sep 12 '14 at 19:54 # Haskell: 81, 77 74 chars f[x]=x f[x,y]=x++" and "++y f[x,y,z]=x++", "++f[y++",",z] f(x:y)=x++", "++f y  Haskell features: Pattern matching • You can remove some spaces – Ray Sep 12 '14 at 18:19 • You could just remove f[x,y,z] – seequ Sep 12 '14 at 18:37 • And now, it is pretty much standard recursion. :) – seequ Sep 12 '14 at 18:40 • you forgot the oxford comma and spaces after the commas - f["a","b","c"] is supposed to be "a, b, and c" but it is "a,b and c" – proud haskeller Sep 12 '14 at 18:49 • how about golfing by replacing y++", and "++z by f[y++",",z] :) – proud haskeller Sep 12 '14 at 19:19 ## Ruby, 47 bytes q=" and ";p$*[2]?(a=$*.pop;$**", "+?,+q+a):$**q  ### Explanation • The input are the command line arguments ($*).
• When $* has a third element ($*[2] does not return nil), take all elements minus the last one and turn them into a comma-separated String using Array#*. Finally add an extra comma, the string " and " and the last command line argument.
• When $* has no third element, two, one, or zero arguments were given. The arguments can safely be joined with the string " and " and produce the correct result. # Python 2 (61) s=input() d=s.pop() print", ".join(s)+", and "[8-7*len(s):]+d  The main trick is to cut off part of the final joiner ", and " for one and two elements. For one, all of it is cut out, and for two, the comma is removed. This is done by slicing out [8-7*len(s):] (noting that s is one shorter after the pop). Unfortunately, d cannot just be replaced with its expression or the pop would happen too late. • You could replace the first s with s=input() and remove the first line, if I'm not mistaken. Saves 2 chars. – tomsmeding Sep 13 '14 at 6:33 • @tomsmeding I don't understand what you're suggesting. The code is referring to s multiple times. – xnor Sep 13 '14 at 7:07 • Wait, I do weird stuff this morning. Forget that. :) – tomsmeding Sep 13 '14 at 7:16 # CSS, 62 chars 112 chars Inspired by the other entries, even shorter. Note that it requires that A elements are not separated by white spaces: a+a:before{content:", "}a+a:last-child:before{content:", and "  http://jsfiddle.net/olvlvl/1Ls79ocb/ Fix for the "one and two", as pointed at by 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/ # Perl (v 5.10+) - 373534 28 @F>2&&s/ /, /g;s/.* \K/and /  to be run with perl -ape, with the list supplied space separated on STDIN. Output for various inputs: $ 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

• You can reduce this by 3 bytes by changing your last substitution to s/(\S+)$/and$1/ – ThisSuitIsBlackNot Sep 17 '14 at 22:19
• @ThisSuitIsBlackNot You can drop the '\n' (thanks), but you can't drop the spaces, or an input of "x" becomes "and x" – abligh Sep 18 '14 at 6:41
• 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 Sep 18 '14 at 14:21 • 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 Sep 18 '14 at 15:14
• @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 Sep 18 '14 at 17:12

# Javascript (63)

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

Cases:

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

Caveat: this will modify the last element in an array with length > 2.

• Nice use of && – Chris Bloom Sep 12 '14 at 20:04

# 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 Sep 11 '14 at 19:24
• The second output should be 1 and 2 instead of 1, 2 – Optimizer Sep 11 '14 at 19:40
• @Optimizer - quite right. Fixed. – Digital Trauma Sep 11 '14 at 20:11

# 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.

## 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).

# 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.

# 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).

# PHP, 192167146 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? – user16402 Sep 12 '14 at 18:36
• Yes, sorry. I was on my phone and it wouldn't paste the code properly. I'll update from my desktop – Chris Bloom Sep 12 '14 at 18:37
• @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 Sep 12 '14 at 18:45
• Apologies again. First time poster at CG. I've fixed my answer – Chris Bloom Sep 12 '14 at 19:06

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


## 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. • The Oxford comma is the comma before the conjunction. – Dennis Sep 11 '14 at 21:09 # PHP, 86 84 chars $a = ['one', 'two', 'three', 'four', 'five'];


With the array initialized, we start counting:

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


The last item in the list is modified. This should be OK because in PHP, array assignment and function calls perform copies.

# CJam, 353028 27 bytes

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


This is a program that reads from STDIN and prints to STDOUT. Try it online.

### How it works

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


### Example run

$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

• I am on your tail - see below :-) – abligh Sep 18 '14 at 22:50

# Google Sheets, 676867 92 bytes

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


The input starts at cell A1 and continuing down for however many entries exist.
JOIN merges them all into a string with a comma-space between each.
FILTER removes any non-blanks so you don't end up with infinite commas at the end.
SUBSTITUTE replaces the last comma (found by COUNTA counting the non-blank inputs).

It's not very exciting but it's doable in a single cell.

• This leaves out the oxford comma. – Umbrella Oct 6 '17 at 21:12
• @Umbrella Well, that was stupid of me, wasn't it? +1 bytes – Engineer Toast Oct 9 '17 at 13:44
• You should be able to drop the terminal ) off of this formula for -1 Byte; Well dang, that was the quickest edit I've ever seen +1 – Taylor Scott Oct 9 '17 at 19:37
• Two notes after testing this - A:A>"" should be converted to A:A<>"" and this does not implement omitting the oxford comma on cases of only 2 objects – Taylor Scott Oct 9 '17 at 20:08
• @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 Oct 9 '17 at 20:23

# 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

• How do I run this? I tried it in node --harmony with an array of strings var a = 'abcdefgh'.split('');, but it just sits there with a ... and appears to do nothing. Also +1 for using unique ES6 feature ()=>. – Adrian Sep 12 '14 at 4:51
• @Adrian the function returns a string, without any output. I'll add a test in the answer. – edc65 Sep 12 '14 at 6:41
• I think f=(a,b=a.pop())=>a[0]?a.join(', ')+', and '+b:b is also correct and 13 bytes shorter. – Ingo Bürk Sep 12 '14 at 14:23
• @IngoBürk that cuts the part that handles the comma having 2 items. Guess what? With 2 items the output is wrong. – edc65 Sep 12 '14 at 14:30
• @edc65 Ah, d'oh. May bad. – Ingo Bürk Sep 12 '14 at 15:03

# JavaScript - 60567167 63

Not exactly an idiomatic approach, but I had fun writing it and I like regex.

Assuming the array is stored in var a:

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


Shortened by simply checking for index [2]: (yay boolean coercion)

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


Apparently I suck at testing and skipped a single-entry test. Here's the fixed version:

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


Shaved off 2 chars by inverting my booleans and 3 more by moving the space from the concatenation into the then/else of the first ternary:

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.

• Liking regex is known as masochism. – seequ Sep 12 '14 at 19:05
• Actually you didn't need the !! at all; a[2]?A:B works already. Might have to invert your booleans again. – tomsmeding Sep 13 '14 at 6:36
• @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 Sep 13 '14 at 7:11
• @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 Sep 13 '14 at 7:14

## Golfscript - 313934 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 Sep 14 '14 at 19:39
• Edited for a more efficient algorithm. – Josiah Winslow Sep 15 '14 at 4:07
• The battle continues. :P – Dennis Sep 15 '14 at 5:26
• Ah, but now we're even. :P – Josiah Winslow Sep 15 '14 at 5:59

# Xojo, 5271 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 Sep 18 '14 at 9:52
• Good catch. Edited to fix. – silverpie Sep 28 '14 at 3:18

# 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]


# 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 Sep 12 '14 at 18:29

# Python 62 chars

Assuming that i is the list of strings:

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


# 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

• I don't know Julia, but this looks like it doesn't generate the Oxford comma. – flornquake Sep 18 '14 at 9:32
• @flornquake it does print with the Oxford comma, due to the optional "last" argument – Cruor Sep 18 '14 at 16:49
• It should be 1, 2, and 3, not 1, 2 and 3. – flornquake Sep 18 '14 at 21:11

# 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 # 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 Oct 21 '14 at 13:55
• @Dennis I'll add some test cases. – isaacg Oct 21 '14 at 15:33
• 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 Oct 21 '14 at 16:58
• @Dennis Yeah, the adding to list rule was added quite recently. – isaacg Oct 21 '14 at 19:33

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