Генераторы и цикл for
Содержание
Цикл for.
Цикл for может использоваться для различных целей.
Самый простой пример использования цикла:
for i in range(5): print(i)
0 1 2 3 4
При помощи этого цикла можно итерироваться по любому объекту-коллекции:
lst = ["qwerty", 12345, 34.42] for i in lst: print(i)
qwerty 12345 34.42
Но в таком случае встает вопрос, что же общего между объектом-коллекцией и диапазоном значений? range является функцией. Попробуем посмотреть, что эта функция возвращает:
a = range(5) print("object:\n\t", a) print("type:\n\t", type(a)) print("Methods and attributes:\n\t", dir(a))
object: range(0, 5) type: <class 'range'> Methods and attributes: ['__bool__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index', 'start', 'step', 'stop']
То есть range -- это класс и мы вызываем его конструктор. Объект этого класса является итерируемым, а значит с ним может работать цикл for. Чтобы создать итератор из объекта, воспользуемся функцией iter():
iterator = iter(a) print("object:\n\t", iterator) print("type:\n\t", type(iterator)) print("Methods and attributes:\n\t", dir(iterator))
object: <range_iterator object at 0x0000012FA12F9CF0> type: <class 'range_iterator'> Methods and attributes: ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__']
Итератор — объект, который знает свое текущее состояние и может вычислить следующее значение. Такой подход не приводит к созданию дополнительных больших объектов в памяти и таким образом делает программу более эффективной. Никакой лишней информации при этом в памяти не хранится.
Для того, чтобы перейти к следующему состоянию, используется функция next().
print(next(iterator)) print(next(iterator)) print(next(iterator)) print(next(iterator)) print(next(iterator))
0 1 2 3 4
Но что же происходит, когда мы пытаемся получить следующий объект, но его не существует?
next(iterator)
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-19-4ce711c44abc> in <module>() ----> 1 next(iterator) StopIteration:
В таком случае выпадает ошибка StopIteration, которая говорит, что следующий объект получить невозможно. Это и является признаком конца итерации. На эту ошибку и ориентируется цикл for.
Упражнение 1
Вам дана функция на языке python:
def print_map(function, iterable): for i in iterable: print(function(i))
Требуется переписать данную функцию не используя цикл for. ****
Генераторы
Рассмотрим несколько примеров итерируемых объектов, которые есть в языке python (кроме range).
map(function, iterable)
В начале рассмотрим функцию map(func, iterable). Эта функция позволяет применить некоторую другую функцию func ко всем элементам другого итерируемого объекта iterable. Обратите внимание, что объект-функция передается без круглых скобок
def baz(value): return value * value lst = [1, 2, 3, 4, 5] for i in map(baz, lst): print(i)
1 4 9 16 25
zip(iterable[, iterable, ...])
Функция zip(iterable[, iterable, ...]) позволяет параллельно итерироваться по большому количеству итерируемых объектов, получая из них соответствующие элементы в виде кортежа. Итератор прекращает свою работу, когда один из переданных объектов закончится.
names = ["Alex", "Bob", "Alice", "John", "Ann"] age = [25, 17, 34, 24, 42] sex = ["M", "M", "F", "M", "F"] for values in zip(names, age, sex): print("name: {:>10} age: {:3} sex: {:2}".format(*values))
name: Alex age: 25 sex: M name: Bob age: 17 sex: M name: Alice age: 34 sex: F name: John age: 24 sex: M name: Ann age: 42 sex: F
filter(func, iterable)
Пробегает по итерируемому объекту и возвращает только те элементы, которые удовлетворяют условию, описанному в функции func.
def bar(x): if abs((34-x*x))**0.5 > x: return True return False for i in filter(bar, [0, 1, 2, 3, 4, 5]): print(i)
0 1 2 3 4
enumerate(iterable, start=0)
Принимает на вход итерируемый объект и возвращает пары (индекс элемента, элемент). Индексация начинается со start, который по умолчанию равен 0.
names = ["Alex", "Bob", "Alice", "John", "Ann"] for idx, elem in enumerate(names, 1): print("{:02}: {:>7}".format(idx, elem))
01: Alex 02: Bob 03: Alice 04: John 05: Ann
Кажется, что концепция генерации объектов налету, без предварительного выделения памяти под целый массив, является довольно удобной и полезной. Объекты-итераторы могут хранить, например, списки запросов к серверу, логи системы и другую информацию, которую можно обрабатывать последовательно. В таком случае, нам хочется научиться создавать подобные объекты.
Для этих целей может использоваться ключевое слово yield. Функция, в которой содержится это ключевое слово, становится функцией-генератором. Из такой функции можно создать объект-итератор. При вызове функции next() выполнение этой функции дойдет до первого встреченного ключевого слова yield, после чего, подобно действию return, управление перейдет основной программе. Поток управления вернется обратно в функцию при следующем вызове next() и продолжит выполнение с того места, на котором остановился ранее.
Рассмотрим, каким образом можно написать свою собственную функцию range():
def my_range(a, b=None, step=1): if b is None: a, b = 0, a _current = a while True: yield _current _next = _current + step if (_next - b)*(_current - b) <= 0: break _current = _next for i in my_range(5): print(i, end = " ") print() for i in my_range(1, 5): print(i, end = " ") print() for i in my_range(1, 10, 2): print(i, end = " ") print() for i in my_range(10, 0, -3): print(i, end = " ") print()
0 1 2 3 4 1 2 3 4 1 3 5 7 9 10 7 4 1
Упражнение 2
Напишите генератор, выводящий первые n чисел Фибоначчи. ***
Упражнение 3
Реализуйте аналог функций zip, map, enumerate. ***
Большое количество различных итерируемых объектов содержится в библиотеке itertools. Функции приведены в таблицах ниже:
Iterator | Arguments | Results | Example |
---|---|---|---|
count()_ | start, [step] | start, start+step, start+2*step, … | count(10) --> 10 11 12 13 14 ... |
cycle()_ | p | p0, p1, … plast, p0, p1, … | cycle('ABCD') --> A B C D A B C D ... |
repeat()_ | elem [,n] | elem, elem, elem, … endlessly or up to n times | repeat(10, 3) --> 10 10 10 |
Iterator | Arguments | Results | Example |
---|---|---|---|
accumulate()_ | p [,func] | p0, p0+p1, p0+p1+p2, … | accumulate([1,2,3,4,5]) --> 1 3 6 10 15 |
chain()_ | p, q, … | p0, p1, … plast, q0, q1, … | chain('ABC', 'DEF') --> A B C D E F |
chain.from_iterable()_ | iterable | p0, p1, … plast, q0, q1, … | chain.from_iterable(['ABC', 'DEF']) --> A B C D E F |
compress()_ | data, selectors | (d[0] if s[0]), (d[1] if s[1]), … | compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F |
dropwhile()_ | pred, seq | seq[n], seq[n+1], starting when pred fails | dropwhile(lambda x: x<5, [1,4,6,4,1]) --> 6 4 1 |
filterfalse()_ | pred, seq | elements of seq where pred(elem) is false | filterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8 |
groupby()_ | iterable[, key] | sub-iterators grouped by value of key(v) | |
islice()_ | seq, [start,] stop [, step] | elements from seq[start:stop:step] | islice('ABCDEFG', 2, None) --> C D E F G |
starmap()_ | func, seq | func(*seq[0]), func(*seq[1]), … | starmap(pow, [(2,5), (3,2), (10,3)]) --> 32 9 1000 |
takewhile()_ | pred, seq | seq[0], seq[1], until pred fails | takewhile(lambda x: x<5, [1,4,6,4,1]) --> 1 4 |
tee()_ | it, n | it1, it2, … itn splits one iterator into n | |
zip_longest()_ | p, q, … | (p[0], q[0]), (p[1], q[1]), … | zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D- |
Iterator | Arguments | Results |
---|---|---|
product()_ | p, q, … [repeat=1] | cartesian product, equivalent to a nested for-loop |
permutations()_ | p[, r] | r-length tuples, all possible orderings, no repeated elements |
combinations()_ | p, r | r-length tuples, in sorted order, no repeated elements |
combinations_with_replacement()_ | p, r | r-length tuples, in sorted order, with repeated elements |
Examples | Results |
---|---|
product('ABCD', repeat=2) | AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD |
permutations('ABCD', 2) | AB AC AD BA BC BD CA CB CD DA DB DC |
combinations('ABCD', 2) | AB AC AD BC BD CD |
combinations_with_replacement('ABCD',2) | AA AB AC AD BB BC BD CC CD DD |
Упражнение №1
Написать функцию, принимающую 2 списка и возвращающую декартово произведение (использовать itertools.product)
def get_cartesian_product(a, b): raise RuntimeError("Not implemented") get_cartesian_product([1, 2], [3, 4]) == [(1, 3), (1, 4), (2, 3), (2, 4)]
Упражнение №2
Написать функцию, принимающую строку s и число n и возвращающую всевозможные перестановки из n символов в s строке в лексикографическом(!) порядке (использовать itertools.permutations)
def get_permutations(s, n): raise RuntimeError("Not implemented") get_permutations("cat", 2) == ["ac", "at", "ca", "ct", "ta", "tc"]
Упражнение №3
Реализовать функцию get_combinations. Должна принимать строку s и число k и возвращать все возможные комбинации из символов в строке s с длинами <= k (использовать itertools.combinations)
def get_combinations(s, n): raise RuntimeError("Not implemented") get_combinations("cat", 2) == ["a", "c", "t", "ac", "at", "ct"]
Упражнение №4
Функция должна принимать строку s и число k и возвращать все возможные комбинации из символов в строке s с длинами = k с повторениями (использовать itertools.combinations_with_replacement)
def get_combinations_with_r(s, n): raise RuntimeError("Not implemented") get_combinations_with_r("cat", 2) == ["aa", "ac", "at", "cc", "ct", "tt"]
Упражнение №5
Написать функцию, которая подсчитывает количество подряд идующих символов в строке (использовать itertools.groupby)
def compress_string(s): raise RuntimeError("Not implemented") compress_string('1222311') == [(1, 1), (3, 2), (1, 3), (2, 1)]
Упражнение №6
В функцию передается список списков. Нужно вернуть максимум, который достигает выражение $(a_1^2 + a_2^2 + ... + a_n^2) % m $. Где ai --- максимальный элемент из i-ого списка (использовать функцию из itertools)
def maximize(lists, m): raise RuntimeError("Not implemented") lists = [ [5, 4], [7, 8, 9], [5, 7, 8, 9, 10] ] maximize(lists, m=1000) == 206
В примере = 206, так как это максимум от суммы (a21 + a22 + a23)%1000
a1 = 5, a2 = 9, a3 = 10