3

complexstring: '2,3-5,50-52,70'

output required: [2,3,4,5,50,51,52,70]

Here is what I attempted and succeeded

a = '2,3-5,50-52,70'
data = []
[data.extend(range(int(r.split('-')[0]),int(r.split('-')[1])+1)) if r.find('-') != -1 else data.append(int(r)) for r in a.split(',')]
print data

output achieved: [2, 3, 4, 5, 50, 51, 52, 70]

But my question is there a way to do in place with in list comprehension? What I exactly mean is

data = [#perform some processing on a here to get directly output]

instead of pre-declaring list data and keep appending or extending it.

P.S: I want to achieve it with just list comprehension without defining additional function.

be_good_do_good
  • 4,311
  • 3
  • 28
  • 42
  • 2
    *P.S: I want to achieve it with just list comprehension without defining additional function.* I understand wanting to see how this is done out of *interest*, but this is absolutely worth writing a separate function for (e.g., one for which `f('3')` returns `[3]` and `f('3-6')` returns `[3, 4, 5, 6]`.) Then you can write `[x for group in a.split(',') for x in f(group)]`, I believe. – Lynn Sep 09 '16 at 16:39
  • 1
    @Lynn I do have a function exactly as what you said (upvoted your comment). But just wanted to do using list comprehension in place. – be_good_do_good Sep 09 '16 at 16:44

2 Answers2

3

I doubt it's possible to do it with a single list comprehension. You could abuse sum() and do other horrible things in one line, though:

sum([range(*(2 * map(int, c.split('-')))[:2]) + [int(c.split('-')[-1])] for c in text.split(',')], [])

A cleaner way would be to use a generator:

def parse_sequence(text):
    for chunk in text.split(','):
        parts = map(int, chunk.split('-'))

        if len(parts) == 1:
            yield parts[0]
        else:
            for n in xrange(parts[0], parts[1] + 1):
                yield n

Please don't do it in one line. The second approach is twice as fast and isn't written to be hard to read. There's no reason to use the first one.

Blender
  • 289,723
  • 53
  • 439
  • 496
  • Upvoted, thanks for answer. I used similar approach like your parse_sequence. But just wanted to achieve it in one-liner. As other answer came 6 minutes early than yours, accepting other answer. Thanks for your understanding. – be_good_do_good Sep 09 '16 at 17:05
  • Agreed that generator is the way (less memory allocations & copy/paste). Run Blender code several times it will catch up with the code of my fast answer :) – Jean-François Fabre Sep 09 '16 at 17:09
  • @Blender: would that line work in python 3? `parts = map(int, chunk.split('-'))` I think you'd have to explicitly convert to a list because `map` has become a generator function. – Jean-François Fabre Sep 09 '16 at 17:14
  • @Jean-FrançoisFabre: Yeah, and replace `xrange` with `range`. The question was tagged [tag:python-2.7], so I didn't bother making it work for Python 3. – Blender Sep 09 '16 at 21:35
2

Oneliner:

a = '2,3-5,50-52,70'
data = sum([[int(x)] if x.isdigit() else list(range(int(x.split('-')[0]),1+int(x.split('-')[1]))) for x in a.split(",")],[])
print(data)

variant without isdigit:

data = sum([list(range(int(x.split('-')[0]),1+int(x.split('-')[1]))) if "-" in x else [int(x)] for x in a.split(",")],[])

(I use sum with a start value of [] which allows to "flatten" the created lists of one level)

result:

[2, 3, 4, 5, 50, 51, 52, 70]

That said, that's just for fun:

  • I don't know if the performance is good, though, since there are a lot of list creations and split calls that could be avoided (actually I think it's bad)
  • It's rather easy to build, but hard to maintain/understand
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • I didn't know you can use sum like that. Interesting. – MooingRawr Sep 09 '16 at 16:37
  • I didn't either :) you can define a start value in `sum`: defaults to `0` but you can pass anything that can be added together apparently. – Jean-François Fabre Sep 09 '16 at 16:38
  • Is that by design? or a by product of it's definition? – MooingRawr Sep 09 '16 at 16:41
  • @MooingRawr I feel like it’s by design; if Python devs wanted `sum` to only handle numbers, they probably wouldn’t have added that second parameter. [It throws an error if you try to do `sum(['a','b'],'')`, though.](http://stackoverflow.com/questions/3525359/python-sum-why-not-strings) – Lynn Sep 09 '16 at 16:44
  • @Lynn: sounds to me that's to avoid a performance issue: `sum() can't sum strings [use ''.join(seq) instead]`. `join` can precompute string length and avoids a lot of reallocations, the main issue when +='ing strings. – Jean-François Fabre Sep 09 '16 at 16:47
  • @Jean-FrançoisFabre Thanks for the answer, liked the approach. Can you also comment on performance impact of doing it in multiple steps rather in one-liner, if possible? I feel doing it in multiple steps is faster. Anyways I will time it and post my observations – be_good_do_good Sep 09 '16 at 17:09
  • 1
    It is faster just for the `split` operation that you have to do only once, and also for the avoiding of creating a list with only 1 element. – Jean-François Fabre Sep 09 '16 at 17:12