I would like to offer an alternative solution, which to me, looks much simpler
and easier to work with.
It's because it looks exactly like a problem which could be very easily solved using
left fold, which is exactly what reduce
is in python
(http://en.wikipedia.org/wiki/Fold_%28higher-order_function%29)
reduce(function, iterable[, initializer])
Apply function of two arguments cumulatively to the items of iterable, from left to right, so as to reduce the iterable to a single value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). The left argument, x, is the accumulated value and the right argument, y, is the update value from the iterable. If the optional initializer is present, it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty. If initializer is not given and iterable contains only one item, the first item is returned. Roughly equivalent to:
Simply put, I would process iterable
, which would be the line_nums
one value at a time using provided function
, which will decide
if the value is part of already created sequences or not. That way I would end
up with a list of lists representing consecutive number sequences. Then I
would convert them to range (xx-yy
) or just single value (xx
) strings.
So my solution would look like this:
def make_sequences(sequences, val):
if sequences != [] and sequences[-1][-1] == val - 1:
return sequences[:-1] + [sequences[-1] + [val]]
return sequences + [[val]]
def sequence_to_string(s):
return '%s-%s' % (s[0], s[-1]) if len(s) > 1 else str(s[0])
def get_line_numbers_concat(line_nums):
return ', '.join(
sequence_to_string(seq)
'%s-%s' % (seq[0], seq[-1])
for seq in reduce(make_sequences, line_nums, [])
)
The sequence_to_string(..)
and get_line_numbers_concat(..)
functions are
pretty straightforward, so I'll just explain what happens inside
make_sequences(..)
:
def make_sequences(sequences, val):
On the first call he sequences
will be []
(as this was passed to reduce
in get_line_numbers_concat(..)
), on subsequent calls, this is where the
resulting list of sequences will be build - the results of
make_sequences(..)
will be passed as sequences
to subsequent calls of
make_sequences(..)
. To make it clear, this is how it would get called using
the original line_nums
:
make_sequences([], 10007)
==> [[10007]]
make_sequences([[10007]], 10008)
==> [[10007, 10008]]
...
make_sequences([[10007, 10008, 10009, 10010, 10011]], 10013)
==> [[10007, 10008, 10009, 10010, 1011], [10013]]
...
Then we only have to decide, if the val
belongs to the last sequence in
sequences
:
if sequences != [] and sequences[-1][-1] == val - 1: # (1)
This makes sure that sequences
are not empty (otherwise we would get
index error), and then we check if the last number in the last sequence in
sequences (i.e. sequences[-1][-1]
is equal to val - 1
and therefore that
val
should be appended to this last sequence.
This is done here:
return sequences[:-1] + [sequences[-1] + [val]]
where we take all sequences except the last one (sequences[:-1]
) and append
to them a new sequence which is a result of appending val
to the last
sequence.
If however the condition (1)
is not true - which means either there are no
previous sequences (seqences == []
) or the last number of the last sequence
is not exactly one less than val
. In that case we add a new sequence with
only one value val
:
return sequences + [[val]]