24
\$\begingroup\$

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?

\$\endgroup\$
  • 6
    \$\begingroup\$ 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. \$\endgroup\$ – Digital Trauma Sep 11 '14 at 18:58
  • 7
    \$\begingroup\$ This would be more interesting with real sentences: ['we invited the stripper','JFK','Stalin'] \$\endgroup\$ – ThisSuitIsBlackNot Sep 11 '14 at 21:06
  • 1
    \$\begingroup\$ Can we assume that the strings themselves don't contain commas already? \$\endgroup\$ – Martin Ender Sep 11 '14 at 21:16
  • 2
    \$\begingroup\$ Challenge should have been titled "Who gives a ---- about an Oxford comma?" \$\endgroup\$ – Igby Largeman Sep 12 '14 at 7:14
  • 3
    \$\begingroup\$ @OldCurmudgeon I think you mean "Americanized rubbish" ;) \$\endgroup\$ – ThisSuitIsBlackNot Sep 21 '14 at 18:59

42 Answers 42

1
\$\begingroup\$

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]}"}
\$\endgroup\$
  • \$\begingroup\$ I think you forgot the Oxford comma \$\endgroup\$ – Asone Tuhid Mar 3 '18 at 17:16
1
\$\begingroup\$

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

\$\endgroup\$
0
\$\begingroup\$

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
...
\$\endgroup\$
  • 1
    \$\begingroup\$ i 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 \$\endgroup\$ – undergroundmonorail Sep 11 '14 at 22:25
  • \$\begingroup\$ @undergroundmonorail The title reads 'generating a string' and the specification reads 'should yield' with nothing said about printing said string. \$\endgroup\$ – Sammitch Sep 11 '14 at 22:39
0
\$\begingroup\$

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:

\$\endgroup\$
0
\$\begingroup\$

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/

\$\endgroup\$
  • 1
    \$\begingroup\$ Missing the Oxford comma: one, two and three should be one, two, and three. \$\endgroup\$ – orome Sep 14 '14 at 13:09
0
\$\begingroup\$

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)} )
}
\$\endgroup\$
0
\$\begingroup\$

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

\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – abligh Sep 14 '14 at 12:02
  • 2
    \$\begingroup\$ You're missing a couple of tricks... say Moose'Util'english_list@F \$\endgroup\$ – tobyink Sep 14 '14 at 13:31
  • \$\begingroup\$ Perl has anonymous functions, and those are acceptable answers here. You can just start with sub{, and not give it a name at all. \$\endgroup\$ – user62131 Mar 25 '17 at 22:22
0
\$\begingroup\$

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"
\$\endgroup\$
0
\$\begingroup\$

PHP, 51 bytes

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

Try it online!

\$\endgroup\$
  • \$\begingroup\$ This leaves out the oxford comma. \$\endgroup\$ – Umbrella Oct 6 '17 at 21:13
0
\$\begingroup\$

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);
\$\endgroup\$
0
\$\begingroup\$

SmileBASIC, 80 bytes

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

Ruby, 44 bytes

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

Try it online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.