Iterators
Contents
Iterators#
Most container objects can be looped over using a for statement:
for element in [1, 2, 3]:
print(element, end=' ')
1 2 3
for element in (1, 2, 3):
print(element, end=' ')
1 2 3
for key in {'one': 1, 'two': 2}:
print(key, end=' ')
one two
for char in "123":
print(char, end=' ')
1 2 3
for line in open("../environment.yml"):
print(line, end= ' ')
channels:
- sympy
- conda-forge
- defaults
dependencies:
- cython
- fortran-magic
- h5py
- imageio
- ipykernel
- ipywidgets
- joblib
- jupytext
- line_profiler
- lorem
- matplotlib
- memory_profiler
- numba
- numexpr
- numpy
- pillow
- progressbar2
- pythran
- scipy
- seaborn
- setuptools
- sympy
- tqdm
- ujson
- pip
- pip:
- py-heat-magic
- jupyter-book
The
for
statement callsiter()
on the container object.The function returns an iterator object that defines the method
__next__()
To add iterator behavior to your classes:
Define an
__iter__()
method which returns an object with a__next__()
.If the class defines
__next__()
, then__iter__()
can just return self.The StopIteration exception indicates the end of the loop.
s = 'abc'
it = iter(s)
it
<str_iterator at 0x7f3c74526830>
next(it), next(it), next(it)
('a', 'b', 'c')
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
rev = Reverse('spam')
for char in rev:
print(char, end='')
maps
def reverse(data): # Python 3.6
yield from data[::-1]
for char in reverse('bulgroz'):
print(char, end='')
zorglub
Generators#
Generators are a simple and powerful tool for creating iterators.
Write regular functions but use the yield statement when you want to return data.
the
__iter__()
and__next__()
methods are created automatically.
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
for char in reverse('bulgroz'):
print(char, end='')
zorglub
Exercise#
Generates a list of IP addresses based on IP range.
ip_range =
for ip in ip_range("192.168.1.0", "192.168.1.10"):
print(ip)
192.168.1.0
192.168.1.1
192.168.1.2
...
Generator Expressions#
Use a syntax similar to list comprehensions but with parentheses instead of brackets.
Tend to be more memory friendly than equivalent list comprehensions.
sum(i*i for i in range(10)) # sum of squares
285
%load_ext memory_profiler
%memit doubles = [2 * n for n in range(10000)]
peak memory: 74.95 MiB, increment: 0.59 MiB
%memit doubles = (2 * n for n in range(10000))
peak memory: 75.09 MiB, increment: 0.00 MiB
# list comprehension
doubles = [2 * n for n in range(10)]
for x in doubles:
print(x, end=' ')
0 2 4 6 8 10 12 14 16 18
# generator expression
doubles = (2 * n for n in range(10))
for x in doubles:
print(x, end=' ')
0 2 4 6 8 10 12 14 16 18
Exercise#
The Chebyshev polynomials of the first kind are defined by the recurrence relation
\begin{align} T_o(x) &= 1 \ T_1(x) &= x \ T_{n+1} &= 2xT_n(x)-T_{n-1}(x) \end{align}
Create a class
Chebyshev
that generates the sequence of Chebyshev polynomials
itertools#
zip_longest#
itertools.zip_longest()
accepts any number of iterables
as arguments and a fillvalue keyword argument that defaults to None.
x = [1, 1, 1, 1, 1]
y = [1, 2, 3, 4, 5, 6, 7]
list(zip(x, y))
from itertools import zip_longest
list(map(sum,zip_longest(x, y, fillvalue=1)))
[2, 3, 4, 5, 6, 7, 8]
combinations#
loto_numbers = list(range(1,50))
A choice of 6 numbers from the sequence 1 to 49 is called a combination.
The itertools.combinations()
function takes two arguments—an iterable
inputs and a positive integer n—and produces an iterator over tuples of
all combinations of n elements in inputs.
from itertools import combinations
len(list(combinations(loto_numbers, 6)))
13983816
from math import factorial
factorial(49)/ factorial(6) / factorial(49-6)
13983816.0
permutations#
from itertools import permutations
for s in permutations('dsi'):
print( "".join(s), end=", ")
dsi, dis, sdi, sid, ids, isd,
count#
from itertools import count
n = 2024
for k in count(): # replace k = 0; while(True) : k += 1
if n == 1:
print(f"k = {k}")
break
elif n & 1:
n = 3*n +1
else:
n = n // 2
k = 112
cycle, islice, dropwhile, takewhile#
from itertools import cycle, islice, dropwhile, takewhile
L = list(range(10))
cycled = cycle(L) # cycle through the list 'L'
skipped = dropwhile(lambda x: x < 6 , cycled) # drop the values until x==4
sliced = islice(skipped, None, 20) # take the first 20 values
print(*sliced)
6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
result = takewhile(lambda x: x > 0, cycled) # cycled begins to 4
print(*result)
6 7 8 9
product#
ranks = ['A', 'K', 'Q', 'J', '10', '9', '8', '7']
suits = [ '\u2660', '\u2665', '\u2663', '\u2666']
cards = [(rank, suit) for rank in ranks for suit in suits]
len(cards)
from itertools import product
cards = product(ranks, suits)
print(*cards)
('A', '♠') ('A', '♥') ('A', '♣') ('A', '♦') ('K', '♠') ('K', '♥') ('K', '♣') ('K', '♦') ('Q', '♠') ('Q', '♥') ('Q', '♣') ('Q', '♦') ('J', '♠') ('J', '♥') ('J', '♣') ('J', '♦') ('10', '♠') ('10', '♥') ('10', '♣') ('10', '♦') ('9', '♠') ('9', '♥') ('9', '♣') ('9', '♦') ('8', '♠') ('8', '♥') ('8', '♣') ('8', '♦') ('7', '♠') ('7', '♥') ('7', '♣') ('7', '♦')