Expanding a block of numbers in Python
Asked Answered
M

3

9

Before I asked, I did some googling, and was unable to find an answer.

The scenario I have is this: A list of numbers are passed to the script, either \n-delimited via a file, or comma-delimited via a command line arg. The numbers can be singular, or in blocks, like so:

File:

1
2
3
7-10
15
20-25

Command Line Arg:

1, 2, 3, 7-10, 15, 20-25

Both end up in the same list[]. I would like to expand the 7-10 or 20-25 blocks (obviously in the actual script these numbers will vary) and append them onto a new list with the final list looking like this:

['1','2','3','7','8','9','10','15','20','21','22','23','24','25']

I understand that something like .append(range(7,10)) could help me here, but I can't seem to be able to find out which elements of the original list[] have the need for expansion.

So, my question is this: Given a list[]:

['1','2','3','7-10','15','20-25'],

how can I get a list[]:

 ['1','2','3','7','8','9','10','15','20','21','22','23','24','25']
Mcgray answered 12/5, 2015 at 20:47 Comment(1)
possible duplicate of Expand a range which looks like: "1-3,6,8-10" to [1,2,3, 6, 8,9,10]Echolocation
C
14

So let's say you're given the list:

L = ['1','2','3','7-10','15','20-25']

and you want to expand out all the ranges contained therein:

answer = []
for elem in L:
    if '-' not in elem:
        answer.append(elem)
        continue
    start, end = elem.split('-')
    answer.extend(map(str, range(int(start), int(end)+1)))

Of course, there's a handy one-liner for this:

answer = list(itertools.chain.from_iterable([[e] if '-' not in e else map(str, range(*[int(i) for i in e.split('-')]) + [int(i)]) for e in L]))

But this exploits the nature of leaky variables in python2.7, which I don't think will work in python3. Also, it's not exactly the most readable line of code. So I wouldn't really use it in production, if I were you... unless you really hate your manager.

References:  append()  continue  split()  extend()  map()  range()  list()  itertools.chain.from_iterable()  int()

Catnip answered 12/5, 2015 at 20:52 Comment(3)
I totally agree with you solution, I was about to tell the same, but what is the interest of the map ?Wilson
@AlexandreMazel: map return a list in python2, so it saved me nested list comprehensionsCatnip
But range return also a list... Oh yes we want a list of string and not a list of int. I understand now!Wilson
M
2

Input:

arg = ['1','2','3','7-10','15','20-25']

Output:

out = []
for s in arg:
    a, b, *_ = map(int, s.split('-') * 2)
    out.extend(map(str, range(a, b+1)))

Or (in Python 2):

out = []
for s in arg:
    r = map(int, s.split('-'))
    out.extend(map(str, range(r[0], r[-1]+1)))
Mormon answered 12/5, 2015 at 21:6 Comment(0)
E
0

Good old map + reduce will come handy:

>>> elements = ['1','2','3','7-10','15','20-25']
>>> reduce(lambda original_list, element_list: original_list + map(str, element_list), [[element] if '-' not in element else range(*map(int, element.split('-'))) for element in elements])
['1', '2', '3', '7', '8', '9', '15', '20', '21', '22', '23', '24']

Well that would do the trick except that you want 20-25 to also contain 25... so here comes even more soup:

reduce(
    lambda original_list, element_list: original_list + map(str, element_list), 
    [[element] if '-' not in element 
     else range(int(element.split('-')[0]), int(element.split('-')[1]) + 1) 
     for element in elements])

Now even though this works you are probably better off with some for-loop. Well that is a reason why they removed reduce in python 3.

Echolocation answered 12/5, 2015 at 23:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.