Solutions and Discussion for Perl Quiz of the Week #6
Mark Jason Dominus <[email protected]> Tue, 26 Nov 2002 17:47:58 -0500
| Newsgroups | gmane.comp.lang.perl.qotw.quiz-of-the-week |
|---|---|
| Organization | Plover Systems |
| Message-ID | <[email protected]> |
Sample solutions and discussion
Perl Quiz of The Week #6 (20021120)
Write a function, format_number_list, whose argument is a list of
integers. It then returns a string which represents the input list in
compact, human-readable form.
For example,
format_number_list(1, 2, 4, 5, 6, 7, 9, 13, 24, 25, 26, 27)
will return
"1-2, 4-7, 9, 13, 24-27"
Also write a function, 'expand_number_list', which does the conversion
in the opposite direction, so that
expand_number_list("1-2, 4-7, 9, 13, 24-27")
will return
(1, 2, 4, 5, 6, 7, 9, 13, 24, 25, 26, 27)
----------------------------------------------------------------
I'll show solutions for format_number_list first. I'm going to
present two sample solutions this week. One was contributed by
Andreas Koenig:
use Set::IntSpan; # CPAN rules :-)
use strict;
sub format_number_list {
my(@n) = @_;
my $set = Set::IntSpan->new(join ",", @n);
my $run = $set->run_list;
$run =~ s/,/, /g; # give them the spaces
$run;
}
To summarize this solution: Set::IntSpan is a CPAN module that already
does almost exactly what was requested; Andreas simply wrapped it.
Andreas said: "I surely was amazed that nobody found it till monday."
I was amazed also.
Here's a synthetic but straightforward solution, from James Gray,
slightly modified by me:
sub format_number_list {
my @output ;
while (@_) {
my $range_start = shift;
my $range_end = $range_start;
# check if the numbers go in sequence from here
$range_end = shift while (@_ && $_[0] == $range_end + 1);
# ...and add to output accordingly
if ($range_start == $range_end) { push @output, $range_start; }
else { push @output, "$range_start-$range_end"; }
}
join ", ", @output;
}
1. This quiz generated a large amount of discussion about what to do
if the input list was out of order, if the input list contained
repeated numbers, if the input list contained negative numbers, and
so on. had I thought about the problem more carefully before
posting, I would have said something about some of these
situations.
The application I originally had in mind was .newsrc files. Lines
of a .newsrc file indicate which news articles in a newsgroup have
already beenread. News articles are numbered starting from 1, so
the negative-number issue never comes up, and the .newsrc line
represents a set of integers, not a list, so order and repetition
is a nonissue. But I didn't say this in the question.
2. Then there was addition discussion about what the function should
do if given numbers out of order. For example, given (1,2,3,7,4,5,6),
should it produce "1-3, 7, 4-6" or "1-7" or something else? There
were good arguments in both directions:
* Maybe the order is significant; then you don't want to alter it.
* If you sort the input before processing, you foreclose the
possibility of the function ever treating (1, 3, 2)
differently from (1, 2, 3). But if you're careful not to
change the order, the user who wants you to treat them the
same can still call
format_number_list(sort numerically @nums);
* If the purpose of the function is to generate
'human-readable lists' then reordering the numbers for
maximum compression is more likely to achieve that.
For the .newsrc case, since the lists are actually sets, reordering
makes sense. For other applications, it may not. I tested these
separately.
There was also some discussion about whether (3, 2, 1) should turn
into "3-1" or "3, 2, 1", supposing that the input was not to be
reordered.
3. If negative numbers are allowed, the required output format is ugly
and hard to read. (-3, -2, -1) would turn into "-3--1". Some
people opted for "-3..-1" instead.
4. Some people also modified the output formats in various other ways.
Then I had to hack on them to get them to work with the test
harness. You'd be surprised at how difficult it was to make
trivial formatting changes in some of these programs. In several
cases I had to hunt down and change the same punctuation in several
places.
5. With all this discussion, I was surprised to see how few of the
submitted solutions actually worked properly for the
straightforward cases: Postive integers in ascending order with no
repeats. Of the 21 samples I tested, several failed some of the
basic cases. (See
http://perl.plover.com/qotw/misc/r006/RESULTS/format/NOTOK . )
6. Even the programs which were advertised by their authors as
handling certain special cases, often didn't.
7. My conclusion from all this is that maybe people would do well to
pay more attention to basic correctness in the simple cases before
worrying a lot about making the functionality as complete as possible.
Now 'expand_number_list'.
Here's Andreas Koenig's Set::IntSpan version:
use Set::IntSpan; # CPAN rules :-)
use strict;
sub expand_number_list {
my $run = shift;
my $set = Set::IntSpan->new($run);
$set->elements;
}
Here's Tom Varga's, cleaned up a little:
sub expand_number_list {
my @result ;
for my $num (split(/\s* , \s*/x, $_[0])) {
push(@result, ($num =~ /(\d+) \s* - \s* (\d+)/x)
? ($1 .. $2)
: $num
) ;
}
@result ;
}
8. Several people seemed to misunderstand that 'expand_number_list'
was supposed to return a list of numbers, not a string.
That's all for regular quiz #6. I'll send a postmortem of the expert
quiz later on today, and new quizzes tomorrow. Thanks to everyone who
participated.