1

I've looked at a online courses, and they have examples like the following:

from itertools import count
  
# creates a count iterator object
iterator =(count(start = 0, step = 2))
  
# prints an even list of integers
print("Even list:", 
      list(next(iterator) for _ in range(5)))

... which you could write using range or np.arange. Here's another example:

# list containing some strings
my_list =["x", "y", "z"]
  
# count spits out integers for 
# each value in my list
for i in zip(count(start = 1, step = 1), my_list):
    print(i)

... which is basically just enumerate. So my question is: can you give an example of itertools.count and itertools.islice that can't be done (or has to be done much more clunkily) using range?

butterflyknife
  • 1,438
  • 8
  • 17

2 Answers2

4

Here's a situation where the count instance is used sporadically, not immediately in a single loop.

class Foo:
    _x = count()  # Infinite supply of unique integer values

    def __init__(self):
        self._id = f'Foo #{next(self._x)}'

Here's a case where islice is used to prevent O(n) memory usage:

def is_sorted(some_list):
    return all(i <= j for i, j in zip(some_list, islice(some_list, 1, None)))

If you had written that instead as

def is_sorted(some_list):
    return all(i <= j for i, j in zip(some_list, some_list[1:]))

you would have had to make nearly a full copy of some_list before even testing the first pair, which is a huge waste with a large list like [2, 1] + [3] * 10000.


Neither one is necessary, in the sense that each is trivially definable:

def count(start=0, step=1):
    while True:
        yield start
        start += step

# A more accurate translation would be more complicated than necessary for our purposes here.
# The real version would have to be able to handle stop=None
# and choose 1 and -1 as default values for step, depending
# on whether stop is less than or greater than start.
def islice(itr, start, stop, step):
    for _ in range(start):
        next(itr)

    while start < stop:
        yield next(itr)
        start += step
chepner
  • 497,756
  • 71
  • 530
  • 681
2

If you want to save memory usage you can use generators in python or a library could use generators. To slice a generator islice is convenient. count is more flexibel than enumerate.

A very simple generator

import itertools as it
import random
random.seed(42)

def fib():
    x, y = 0, 1
    while True:
        yield x
        x, y = y, x + y

x = random.randint(5, 40)
start, step = 2.5, .1

Using count with floats and islice to slice the generator.

for c, i in zip(it.count(start, step), it.islice(fib(), x, x+5)):
    print(f'{c:.1f}', i)

Output

2.5 144
2.6 233
2.7 377
2.8 610
2.9 987

It's not too difficult to use a generator without itertools, but it does require a bit more effort. So it remains a personal choice of style. There are more ways to implement this, but I tried to be as expressive as itertools and the least clunky.

g = fib()
for c in range(x+5):
    n = next(g)
    if c >= x:
        print(f'{start + (c-x)*step:.1f}', n)

Output

2.5 144
2.6 233
2.7 377
2.8 610
2.9 987
Michael Szczesny
  • 4,911
  • 5
  • 15
  • 32