Generating a single string with an Oxford comma from a list

24

3

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?

orome

Posted 2014-09-11T18:52:03.007

Reputation: 348

Input is an Array of characters or a single string. i.e. ['1','2'] or "['1', '2']" – Optimizer – 2014-09-11T18:56:01.970

6Welcome 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 [tag: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 – 2014-09-11T18:58:35.377

@DigitalTrauma: Good suggestion. Thanks. I'd like it to focus on producing the most idiomatic code; that is, the code that takes best advantage of the capabilities of the language. Is that a fair challenge (and of so, how should I tag it)? – orome – 2014-09-11T19:02:30.900

@Optimizer: An array of characters (of of strings). – orome – 2014-09-11T19:05:14.247

@MartinBüttner: For here, I'll go with length ([tag:code-golf]) then. – orome – 2014-09-11T19:21:58.717

7

This would be more interesting with real sentences: ['we invited the stripper','JFK','Stalin']

– ThisSuitIsBlackNot – 2014-09-11T21:06:05.757

1Can we assume that the strings themselves don't contain commas already? – Martin Ender – 2014-09-11T21:16:04.810

@MartinBüttner: Good question. Assume not: no commas or "and"s to mess things up. – orome – 2014-09-11T21:20:56.587

Do we need to make it a complete CLI-executable solution? Must it be wrapped in a function declaration, or just the relevant code with setup instructions? Is the setup included in the scoring size? – Adrian – 2014-09-12T06:15:08.773

2

Challenge should have been titled "Who gives a ---- about an Oxford comma?"

– Igby Largeman – 2014-09-12T07:14:25.263

My old English teacher would be horrified to see a comma before an "and". The last result should read '1, 2 and 3' to be proper English English rather than the Americanised rubbish spoken in the colonies. :) – OldCurmudgeon – 2014-09-18T10:19:46.373

3@OldCurmudgeon I think you mean "Americanized rubbish" ;) – ThisSuitIsBlackNot – 2014-09-21T18:59:06.763

Answers

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

NinjaBearMonkey

Posted 2014-09-11T18:52:03.007

Reputation: 9 925

1Brilliant! I love it. :D – COTO – 2014-09-11T22:28:15.443

If only you'd use <li>. – slebetman – 2014-09-12T04:01:18.920

1<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 – 2014-09-12T04:07:03.810

Wow... this is very impressive. Cleverness award. – None – 2014-09-12T18:38:02.607

CSS as the top answer? Nice. – Brandon – 2014-09-12T19:54:00.140

Why do you need line breaks? HTML is supposed to just treat them like a space. – trlkly – 2014-09-19T02:12:36.057

@trlkly The line breaks eliminate the need for adding spaces to the CSS code because they are automatically converted to spaces. – NinjaBearMonkey – 2014-09-19T02:15:39.617

13

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

user3389669

Posted 2014-09-11T18:52:03.007

Reputation: 341

You can remove some spaces – Ray – 2014-09-12T18:19:08.053

1You could just remove f[x,y,z] – seequ – 2014-09-12T18:37:05.327

And now, it is pretty much standard recursion. :) – seequ – 2014-09-12T18:40:24.680

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 – 2014-09-12T18:49:12.647

also you could golf a bit by replacing " "++f y by ' ':f y – proud haskeller – 2014-09-12T18:49:58.760

@proudhaskeller Is it still possible with the version including whitespace? – user3389669 – 2014-09-12T18:57:38.583

you still need to add the actual oxford comma :). also, you better cut out the old incorrect version (it is still visible in the edit history). about the ',': optimization, unfortunately not. – proud haskeller – 2014-09-12T18:59:31.140

Currently it gives 1, 2, 3, and 4 – seequ – 2014-09-12T19:04:39.620

@Sieg not on my computer, and not as simulated by my mind – proud haskeller – 2014-09-12T19:07:03.787

@proudhaskeller Now the correct version is there again. – user3389669 – 2014-09-12T19:14:55.023

4how about golfing by replacing y++", and "++z by f[y++",",z] :) – proud haskeller – 2014-09-12T19:19:21.167

7

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.

britishtea

Posted 2014-09-11T18:52:03.007

Reputation: 1 189

6

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.

xnor

Posted 2014-09-11T18:52:03.007

Reputation: 115 687

You could replace the first s with s=input() and remove the first line, if I'm not mistaken. Saves 2 chars. – tomsmeding – 2014-09-13T06:33:13.487

@tomsmeding I don't understand what you're suggesting. The code is referring to s multiple times. – xnor – 2014-09-13T07:07:06.340

Wait, I do weird stuff this morning. Forget that. :) – tomsmeding – 2014-09-13T07:16:08.293

6

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/

olvlvl

Posted 2014-09-11T18:52:03.007

Reputation: 161

5

Perl (v 5.10+) - 37 35 34 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

abligh

Posted 2014-09-11T18:52:03.007

Reputation: 777

You can reduce this by 3 bytes by changing your last substitution to s/(\S+)$/and $1/ – ThisSuitIsBlackNot – 2014-09-17T22:19:32.467

1@ThisSuitIsBlackNot You can drop the '\n' (thanks), but you can't drop the spaces, or an input of "x" becomes "and x" – abligh – 2014-09-18T06:41:02.917

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 – 2014-09-18T14:21:58.913

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 – 2014-09-18T15:14:28.900

@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 – 2014-09-18T17:12:59.440

Okay, two more: replacing the second regex with s/.* \K/and / drops you down by 4 (requires Perl 5.10), and replacing the conditional operator with && shaves off one, for a grand total of 29! Take that, Golfscript! @F>2&&s/ /, /g;s/.* \K/and / – ThisSuitIsBlackNot – 2014-09-18T22:04:11.877

@ThisSuitIsBlackNot: you have excelled yourself. That's actually 28 not 29. I thought I tried && earlier and had operator precedence issues, but I obviously failed. Didn't think of \K. And WOAH! Tied with CJam! Thanks! – abligh – 2014-09-18T22:49:20.270

Oops, I guess I can't count. I knew Perl could hold its own in golf, though. I tried a lot of different approaches and yours was better than all of them, so kudos. All I did was tidy up a bit :P – ThisSuitIsBlackNot – 2014-09-18T23:25:03.000

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
$

Digital Trauma

Posted 2014-09-11T18:52:03.007

Reputation: 64 644

Yes, let's go with [tag:code-golf]. – orome – 2014-09-11T19:24:03.287

The second output should be 1 and 2 instead of 1, 2 – Optimizer – 2014-09-11T19:40:30.340

@Optimizer - quite right. Fixed. – Digital Trauma – 2014-09-11T20:11:39.150

4

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.

mway

Posted 2014-09-11T18:52:03.007

Reputation: 141

2Nice use of && – Chris Bloom – 2014-09-12T20:04:40.770

3

Google Sheets, 67 68 67 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.

Engineer Toast

Posted 2014-09-11T18:52:03.007

Reputation: 5 769

This leaves out the oxford comma. – Umbrella – 2017-10-06T21:12:54.377

@Umbrella Well, that was stupid of me, wasn't it? +1 bytes – Engineer Toast – 2017-10-09T13:44:13.347

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 – 2017-10-09T19:37:13.420

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 – 2017-10-09T20:08:57.427

@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 – 2017-10-09T20:23:05.077

I get what you mean - if you take a look at my solution's edits you can see where I realized I forgot the case of only 2 objects and ended up having to add some 40 bytes just to correct for it – Taylor Scott – 2017-10-10T14:59:47.137

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.

Optimizer

Posted 2014-09-11T18:52:03.007

Reputation: 25 836

6The Oxford comma is the comma before the conjunction. – Dennis – 2014-09-11T21:09:59.533

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.

Falko

Posted 2014-09-11T18:52:03.007

Reputation: 5 307

3

CJam, 35 30 28 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

Dennis

Posted 2014-09-11T18:52:03.007

Reputation: 196 637

I am on your tail - see below :-) – abligh – 2014-09-18T22:50:38.253

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

Martin Ender

Posted 2014-09-11T18:52:03.007

Reputation: 184 808

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.

Οurous

Posted 2014-09-11T18:52:03.007

Reputation: 7 916

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

unclemeat

Posted 2014-09-11T18:52:03.007

Reputation: 2 302

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/

Chris Bloom

Posted 2014-09-11T18:52:03.007

Reputation: 131

Can you please put your code (with Oxford commas) into the answer text? – None – 2014-09-12T18:36:34.807

Yes, sorry. I was on my phone and it wouldn't paste the code properly. I'll update from my desktop – Chris Bloom – 2014-09-12T18:37:59.767

@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 – 2014-09-12T18:45:10.797

Apologies again. First time poster at CG. I've fixed my answer – Chris Bloom – 2014-09-12T19:06:03.607

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

faubi

Posted 2014-09-11T18:52:03.007

Reputation: 2 599

3

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.

Damian Yerrick

Posted 2014-09-11T18:52:03.007

Reputation: 163

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]

Taylor Scott

Posted 2014-09-11T18:52:03.007

Reputation: 6 709

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

edc65

Posted 2014-09-11T18:52:03.007

Reputation: 31 086

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 – 2014-09-12T04:51:09.120

@Adrian the function returns a string, without any output. I'll add a test in the answer. – edc65 – 2014-09-12T06:41:05.393

I think f=(a,b=a.pop())=>a[0]?a.join(', ')+', and '+b:b is also correct and 13 bytes shorter. – Ingo Bürk – 2014-09-12T14:23:44.177

1@IngoBürk that cuts the part that handles the comma having 2 items. Guess what? With 2 items the output is wrong. – edc65 – 2014-09-12T14:30:04.603

@edc65 Ah, d'oh. May bad. – Ingo Bürk – 2014-09-12T15:03:11.793

@edc65 Thanks -- I simultaneously knew and forgot that ()=> was a function definition, so simply pasting it in would have no effect. :/ – Adrian – 2014-09-12T17:14:00.880

2

JavaScript - 60 56 71 67 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.

Adrian

Posted 2014-09-11T18:52:03.007

Reputation: 121

1Liking regex is known as masochism. – seequ – 2014-09-12T19:05:29.750

Actually you didn't need the !! at all; a[2]?A:B works already. Might have to invert your booleans again. – tomsmeding – 2014-09-13T06:36:13.207

@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 – 2014-09-13T07:11:23.250

@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 – 2014-09-13T07:14:50.370

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!

Josiah Winslow

Posted 2014-09-11T18:52:03.007

Reputation: 725

Edited to account for the length 1 array. – Josiah Winslow – 2014-09-14T19:39:10.703

Edited for a more efficient algorithm. – Josiah Winslow – 2014-09-15T04:07:07.080

The battle continues. :P – Dennis – 2014-09-15T05:26:55.697

Ah, but now we're even. :P – Josiah Winslow – 2014-09-15T05:59:09.137

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.

silverpie

Posted 2014-09-11T18:52:03.007

Reputation: 139

This seems to add the comma when there are 2 items - should not – edc65 – 2014-09-18T09:52:37.410

Good catch. Edited to fix. – silverpie – 2014-09-28T03:18:02.827

1

Ruby, 34 bytes

Lambda takes an array as input, and joins every element but the last with the string ,. Then the last element of the array is added on with an "and" between.

->a{a[0..-2]*", "+" and #{a[-1]}"}

snail_

Posted 2014-09-11T18:52:03.007

Reputation: 1 982

I think you forgot the Oxford comma – Asone Tuhid – 2018-03-03T17:16:26.513

1

Haskell, 67 bytes

f[n]=n
f[m,n]=m++" and "++n
f n=(init n>>=(++", "))++"and "++last n

Abuses the list monad a bit

Explanation

Doing this later

Generic Display Name

Posted 2014-09-11T18:52:03.007

Reputation: 365

1

Racket 87

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

Matthew Butterick

Posted 2014-09-11T18:52:03.007

Reputation: 401

I still find the function names in lisps to be way too long for golfing. – seequ – 2014-09-12T18:29:20.920

1

Python 62 chars

Assuming that i is the list of strings:

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

deepy

Posted 2014-09-11T18:52:03.007

Reputation: 121

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

Posted 2014-09-11T18:52:03.007

Reputation: 596

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

Cruor

Posted 2014-09-11T18:52:03.007

Reputation: 31

1I don't know Julia, but this looks like it doesn't generate the Oxford comma. – flornquake – 2014-09-18T09:32:56.663

@flornquake it does print with the Oxford comma, due to the optional "last" argument – Cruor – 2014-09-18T16:49:17.520

2It should be 1, 2, and 3, not 1, 2 and 3. – flornquake – 2014-09-18T21:11:01.467

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

Berkin

Posted 2014-09-11T18:52:03.007

Reputation: 61

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

isaacg

Posted 2014-09-11T18:52:03.007

Reputation: 39 268

I can't seem to figure out how the input should be formatted. – Dennis – 2014-10-21T13:55:55.393

@Dennis I'll add some test cases. – isaacg – 2014-10-21T15:33:00.470

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 – 2014-10-21T16:58:33.440

@Dennis Yeah, the adding to list rule was added quite recently. – isaacg – 2014-10-21T19:33:12.413

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

apathy-

Posted 2014-09-11T18:52:03.007

Reputation: 11

0

bash (79)

(($#<3))&&echo $1 ${2/#/and }||{ Y=${@/%/,};L=${@: -1};echo ${Y/%$L,/and $L}; }

Explaination

For 1 or 2 parameters

  • echo $1 and, if $2 exists, prefix $2 with "and ".

Otherwise

  • suffix all parameters with a comma and join together
  • get last parameter
  • replace "$L," with "and $L"

philcolbourn

Posted 2014-09-11T18:52:03.007

Reputation: 501

0

PHP, 51 bytes

<?=preg_replace("#.*\K,#"," and ",join(",",$_GET));

Try it online!

Jörg Hülsermann

Posted 2014-09-11T18:52:03.007

Reputation: 13 026

This leaves out the oxford comma. – Umbrella – 2017-10-06T21:13:38.070

0

PHP, 67

if($c=count($a)-1)$a[$c]='and '.$a[$c];echo join($c-1?', ':' ',$a);

If there's more than one item, prepend the last with 'and '. (store the count minus one in $c).

Next, join on space or comma space, depending on whether the count is two.

if ($c = count($a) - 1) {
    $a[$c] = 'and '.$a[$c];
}
echo join($c - 1 ? ', ' : ' ', $a);

Umbrella

Posted 2014-09-11T18:52:03.007

Reputation: 867

0

SmileBASIC, 80 bytes

DEF L A
L=LEN(A)WHILE LEN(A)>1?SHIFT(A);","*(L>2);" ";
WEND?"and "*(L>1);A[0]END

12Me21

Posted 2014-09-11T18:52:03.007

Reputation: 6 110

0

Ruby, 44 bytes

->*f,l{f[0]?f*', '+(f[1]??,:'')+' and '+l:l}

Try it online!

Asone Tuhid

Posted 2014-09-11T18:52:03.007

Reputation: 1 944

0

Python, 79 chars

Note: the char count does not include the list declaration.

l=['1','2','3']
print l[0] if len(l)<2 else (', '.join(l[:-1])+(',' if len(l)>2 else '')+' and '+l[-1])

Output for various inputs:

1
1 and 2
1, 2, and 3
...

Sammitch

Posted 2014-09-11T18:52:03.007

Reputation: 509

1i understand why you don't count the declaration (though that is the reason i'm personally against hardcoding input when op doesn't specifically say it's okay) but i'm interested in your justification for not counting print – undergroundmonorail – 2014-09-11T22:25:02.863

@undergroundmonorail The title reads 'generating a string' and the specification reads 'should yield' with nothing said about printing said string. – Sammitch – 2014-09-11T22:39:21.653

0

Golfscript (61)

The fact that Javascript and others score better pretty much shows that this is probably not done very efficiently in terms of golfing.

.,.2={;' and '*}{(\{\(.{.)', '''if}{', and '}if@\+print}/;}if

Examples with link to try it out:

Ingo Bürk

Posted 2014-09-11T18:52:03.007

Reputation: 2 674

0

Perl 5 + Moose, 70 bytes

sub english_list{@_<3?join" and ",@_:do{$_="and ".pop;join", ",@_,$_}}

Not many people seem to know it, but Moose, the popular OO framework for Perl 5, has a function to do this built in. (Moose uses it internally in its error messages, but exposes it as a documented, public function.)

my @l = (1 .. 3);
use Moose;
print Moose::Util::english_list(@l);

Excluding the definition of the list @l, that can be easily golfed down to 41 characters. If you allow the use of the say function which was introduced in Perl 5.10, you can knock off two more characters. (I'll leave it as an exercise for the reader.)

Now, looking at the definition of the english_list function itself, the best I've been able to golf it down to is 70 characters:

sub english_list{@_<3?join" and ",@_:do{$_="and ".pop;join", ",@_,$_}}

... though obviously by choosing a shorter function name, that could be reduced further. (59 characters if you choose a one-letter function name.)

tobyink

Posted 2014-09-11T18:52:03.007

Reputation: 1 233

Perl has anonymous functions, and those are acceptable answers here. You can just start with sub{, and not give it a name at all. – None – 2017-03-25T22:22:44.737

Use perl -ane and use Moose;say Moose::Util::english_list(@F) but that is still 44 characters. You can use the same @F trick for your function. – abligh – 2014-09-14T12:02:33.750

2You're missing a couple of tricks... say Moose'Util'english_list@F – tobyink – 2014-09-14T13:31:06.780

0

CSS, 89

Inspired by the other entry, but shorter :)

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

Demo: http://jsfiddle.net/01gy4Lh1/

xem

Posted 2014-09-11T18:52:03.007

Reputation: 5 523

1Missing the Oxford comma: one, two and three should be one, two, and three. – orome – 2014-09-14T13:09:26.343

0

Groovy - 221 chars

This is not a compact implementation, but the usage is tight: it overrides toString() for ArrayList via meta-class functionality (and maps - no conditionals here!).

This is powerful stuff, but idiomatic when writing a framework. That is, the change is transparent to the client.

Golfed (assumes variable a is defined):

q={s->"'$s'"}
r={q a[it]}
f={"${it[0..-2].join ", "}, and ${it[-1]}"}
m=[:].withDefault{f}
m[0]={""}
m[1]={r 0}
m[2]={"${r 0} and ${r 1}"}
ArrayList.metaClass.toString={->m[delegate.size()].call(delegate.collect{q(it)})}

Sample assertions:

a=['oxford','cambridge','comma','space']
assert "'oxford', 'cambridge', 'comma', and 'space'" == a.toString()

a=['oxford','cambridge']
assert "'oxford' and 'cambridge'" == a.toString()

a=['oxford']
assert "'oxford'" == a.toString()

a=[]
assert "" == a.toString()

Ungolfed:

q = {s->"'$s'"}
r = {q a[it]}

f = {"${it[0..-2].join ", "}, and ${it[-1]}"}
m = [:].withDefault{f}
m[0] = {""}
m[1] = {r 0}
m[2] = {"${r 0} and ${r 1}"}

ArrayList.metaClass.toString = { ->
    m[ delegate.size() ].call( delegate.collect{q(it)} )
}

Michael Easter

Posted 2014-09-11T18:52:03.007

Reputation: 585