0

I'm trying to advance my abilities and learn generator pipeline. I manage to solve a few problems with it but I feel that i have a basic gap in my understanding.

for example: I tried to build a function that returns all numbers within a range. The range given as a string of few ranges. so an example for the string received: "1-2,4-4,8-10". So the first generator should return a list of int couples: [1, 2] [4, 4] [8, 10] and second generator should use the 1st_gen[0] as start and the 1st_gen[1] as a stop in a range function and return all numbers in range: 1 2 4 8 9 10

There is my code, I'll be happy for tips to improve my skills:

def parse_ranges(range_string):
    temp_list = (c.replace("-", ",") for c in list(range_string.split(",")))
    generator2 = (i for start, stop in temp_list for i in range(int(start), int(stop) + 1))
    for i in generator2:
        print(i)

print(parse_ranges("1-2,4-4,8-10"))
print(parse_ranges("0-0,4-8,20-21,43-45"))
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
T.Stimer
  • 255
  • 1
  • 2
  • 8
  • 3
    Please don't post image of code, just copy and paste the code in your question. See [How do I format my posts using Markdown or HTML?](https://stackoverflow.com/help/formatting) – Jorge Morgado Oct 07 '20 at 06:11

2 Answers2

2

Some fixes:

  • str.split() returns a list, no need to put its result into a list
  • parse_ranges() does not return anything, why print it?
  • range(int(4), int(4) + 1) will not return [4, 4] but only [4]

def parse_ranges(range_string):
    temp = (map(int, c.split("-")) for c in range_string.split(",")) 

    # yield all the numbers (yield from 'flattens' the result)
    for start, stop in temp:
        yield from range(start, stop + 1)

    # or to yield lists 
    # for start, stop in temp:
    #     yield list(range(start, stop + 1))

print(*parse_ranges("1-2,4-4,8-10"), sep=" ~ ")
print(*parse_ranges("0-0,4-8,20-21,43-45"), sep=" ~ ")

Output (added '~' as seperator so it is clear, where "borders" of yielded elements are):

# yield from range(start, stop + 1)
1 ~ 2 ~ 4 ~ 8 ~ 9 ~ 10
0 ~ 4 ~ 5 ~ 6 ~ 7 ~ 8 ~ 20 ~ 21 ~ 43 ~ 44 ~ 45

# yield list(range(start, stop + 1))
[1, 2] ~ [4] ~ [8, 9, 10]
[0] ~ [4, 5, 6, 7, 8] ~ [20, 21] ~ [43, 44, 45]
Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • hi Patrick thanks a lot for the sol I wonder, insted of the second loop, how can i replace it with generator? – T.Stimer Oct 07 '20 at 08:53
  • and one more quest if i may, what is the job of the * before func name? – T.Stimer Oct 07 '20 at 09:16
  • 1
    @T.Stimer which second loop? there is only one loop ( `for start,stop in temp:` ) - you use either/or implementation depending on if you want just the numbers or lists of numbers. the * is to decompose the result of the function for printing (so print sees it as several argument to itself, not as 1 big generator expression that it can not print): `print( [1,2,3])` vs `print(*[1,2,3])` – Patrick Artner Oct 07 '20 at 09:24
  • Patrick, your solution contain 1 generator and not generator pipeline... yet is great. thank you :-) I read more about the yield command and all understood now – T.Stimer Oct 07 '20 at 11:19
  • 1
    @T.Stimer - there are in fact 2/3 generators in use here ... `temp` is a [generator-comprehension](https://stackoverflow.com/questions/364802/how-exactly-does-a-generator-comprehension-work), the built in [map(..)](https://docs.python.org/3/library/functions.html#map) function returns an iterator of its yielded values and can be iterated only once and `def parse_ranges(range_string):` yields elements so its also an generator ... – Patrick Artner Oct 07 '20 at 11:22
  • 2
    @PatrickArtner Technically correct, but `temp` is used inside the other generator, so it's not really a (nontrivial) pipeline of generators. – L3viathan Oct 07 '20 at 15:02
2

If you want, you can split the process in as many generator steps as you like, e.g.:

def get_ranges(string):
    for part in string.split(","):
        yield [int(number) for number in part.split("-")]

def get_numbers(ranges):
    for start, stop in ranges:
        yield from range(start, stop + 1)

Usage:

>>> list(get_numbers(get_ranges("1-2,4-4,8-10")))
[1, 2, 4, 8, 9, 10]

If you really wanted, you could even split this into more steps (split into parts, convert into numbers, convert into range objects, get numbers).

L3viathan
  • 26,748
  • 2
  • 58
  • 81