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

39 Answers 39

1
2
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\$
2
  • 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\$ Sep 11, 2014 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, 2014 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\$

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\$
3
  • \$\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, 2014 at 12:02
  • 2
    \$\begingroup\$ You're missing a couple of tricks... say Moose'Util'english_list@F \$\endgroup\$
    – tobyink
    Sep 14, 2014 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, 2017 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, 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\$
1
2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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