Thanks to Rosetta Code, I found out about Eric Lippert’s comma quibbling exercise:
Write me a function that takes a non-null IEnumerable and returns a string with the following characteristics:
- If the sequence is empty then the resulting string is
"{}"
.- If the sequence is a single item “ABC” then the resulting string is
"{ABC}"
.- (3) If the sequence is the two item sequence “ABC”, “DEF” then the resulting string is
"{ABC and DEF}"
.- (4) If the sequence has more than two items, say, “ABC”, “DEF”, “G”, “H” then the resulting string is
"{ABC, DEF, G and H}"
. (Note: no Oxford comma!)
Also note “I am particularly interested in solutions which make the semantics of the code very clear to the code maintainer. (emphasis mine)”
I am going to ignore the requirement that the returned string be enclosed in braces.
Here is the straightforward Perl example that satisfies the clarity requirement:
#!/usr/bin/env perl
use strict;
use warnings FATAL => 'all';
use Test::More;
run();
sub run {
my @cases = (
[[], ''],
[[qw(ABC)], 'ABC'],
[[qw(ABC DEF)], 'ABC and DEF'],
[[qw(ABC DEF G H)], 'ABC, DEF, G and H'],
);
for my $x (@cases) {
my ($case, $expected) = @$x;
ok(comma_quibbling($case) eq $expected);
}
done_testing;
return;
}
sub comma_quibbling {
my $x = shift;
my $n = @$x;
$n or return '';
$n == 1 and return $x->[0];
return join(' and ' =>
# array slice and range operator
join(', ' => @$x[0 .. ($n - 2)]),
$x->[-1],
);
}
If the maintenance programmer has issues with the use of the array slice and range operator above, s/he might use this as an opportunity to learn about them. At the point we construct the range, we know that $n ≥ 2
. If $n == 2
, the slice collapses to the single string $x->[0]
, and the join has no effect.
Now, Eric also says “There’s no size limit on the sequence; it could be tiny, it could be thousands of strings. But it will be finite.” When I get a chance, I’ll compare the solution above to:
sub comma_quibbling {
my $x = shift;
my $n = @$x;
$n or return '';
my $r = $x->[0];
$n == 1 and return $r;
$n == 2 and return ($r .= ' and ' . $x->[1]);
for my $i (1 .. ($n - 2)) {
$r .= ', ' . $x->[$i];
}
return ($r .= ' and ' . $x->[-1]);
}
because I am curious.
PS: See also brian’s post and replies to it on blogs.perl.org.