[ Сборник задач ]
Тема 15. Итераторы

[ Сборник задач ]
Тема 15. Итераторы

Python Workbook Cover T1
  • Операторы: iter
  • Контент: Вопросы (5шт) + задачи (5шт)

Оглавление

1
Введение
Понятие итераторов и итерируемых объектов, их различия, специфика протоколов, особенности применения. Способы создания ленивых итераторов и генераторов.
Перейти
2
Вопросы и ответы
5 вопросов по теме "Итераторы" + ответы
Перейти
3
Условия задач
5 задач по теме двух уровней сложности: Базовый и *Продвинутый
Перейти
4
Решения задач
Приводим код решений указанных выше задач
Перейти
1
One

Введение

Итераторы – специальные объекты для перебора коллекций. Основная их задача – упростить навигацию по отдельным значениям последовательностей или словарей.

Специфика работы итератора такова:
  1. На каждом шаге итерации он обращается к следующему элементу последовательности;
  2. В момент достижения последнего значения возникает ошибка, говорящая о том, что больше элементов нет.
Чаще всего итераторы используются в цикле for, который незаметно для пользователя обрабатывает ошибку окончания коллекции и заканчивает свою работу.

Предлагаемые к решению задания опираются на следующие темы:
  1. Итератор и итерабельный объект, их протоколы;
  2. Функции iter(), next();
  3. Ленивые (lazy) итераторы;
  4. Генераторы (предназначение, yield).

Читайте также

2
Two

Вопросы по теме "Итераторы"

3
Three

Задачи по теме "Итераторы"

Задача 1
Антонина в целях экономии памяти компьютера вместо последовательности чисел от 1 до миллиона в виде списка решила создать ленивый итератор numbers с помощью функции range(). Затем в двух разных местах своего скрипта она делала проверку того, есть ли число 2000 в этой коллекции. В первом случае код вернул True, а во втором False.

Объясните ученице ее ошибку и предложите способ исправления ситуации.

Код – IDE
--
numbers = iter(range(1, 1000001))
print(2000 in numbers)
print(2000 in numbers)


Результат выполнения
---
True
False

Задача 2
Создайте функцию infinite(lst, tries), которая будет проходиться по элементам списка lst (целые числа) заданное количество раз (tries) циклически. Один раз - один элемент списка. После вывода последнего значения последовательности процедура начнется с самого начала.

Например, если в списке 2 элемента, а функция получила значение 3, то сначала выведется первый объект, потом последний, а потом опять первый. Результат работы функции представьте в виде строки, состоящей из tries количества символов.
Задача 3
Инструкция yield позволяет создавать генераторы. В отличие от объявления return в функции, где возвращается один объект, yield при каждом вызове функции генерирует новый объект Фактически это дает возможность использовать генераторы в циклах. Самая важная причина применения такой инструкции - экономия памяти, когда не требуется сохранять всю последовательность, а можно получать ее элементы по одному.

Ученик написал генератор show_letters(some_str), выводящий все символы строки на печать, но только в том случае, если они являются буквами (остальные игнорируются). Сократите код функции.

Код – IDE
---
def show_letters(some_str):
____clean_str = ''.join([letter for letter in some_str if letter.isalpha()])
____for symbol in clean_str:
________yield symbol

Задача 4*
Числа Фибоначчи представляют последовательность, получаемую в результате сложения двух предыдущих элементов. Начинается коллекция с чисел 1 и 1. Она достаточно быстро растет, поэтому вычисление больших значений занимает немало времени. Создайте функцию fib(n), генерирующую n чисел Фибоначчи с минимальными затратами ресурсов.
Для реализации этой функции потребуется обратиться к инструкции yield. Она не сохраняет в оперативной памяти огромную последовательность, а дает возможность "доставать" промежуточные результаты по одному.
Задача 5*
Реализуйте итератор колоды карт (52 штуки) CardDeck. Каждая карта представлена в виде строки типа 2 Пик. При вызове функции next() будет представлена следующая карта. По окончании перебора всех элементов возникнет ошибка StopIteration.
4
Two

Решение

Задача 1. Базовый уровень

Условие

Антонина в целях экономии памяти компьютера вместо последовательности чисел от 1 до миллиона в виде списка решила создать ленивый итератор numbers с помощью функции range(). Затем в двух разных местах своего скрипта она делала проверку того, есть ли число 2000 в этой коллекции. 
В первом случае код вернул True, а во втором False.

Объясните ученице ее ошибку и предложите способ исправления ситуации.
 
Код – IDE
--
numbers = iter(range(1, 1000001))
print(2000 in numbers)
print(2000 in numbers)
 
Результат выполнения
---
True
False
 
В данном случае не было никакого смысла создавать итератор, так как функция range() изначально создает ленивый итерабельный объект, который занимает практически такой же объем памяти.
Решение - IDE

import sys
numbers = iter(range(1, 1000001))
print(sys.getsizeof(numbers))
numbers = range(1, 1000001)
print(sys.getsizeof(numbers))
Результат выполнения

32
48
Как видно, 32 или 48 байт в памяти – никакой разницы, она никак не почувствуется.

Значение False при втором обращении к итератору объясняется тем, что он уже «истощился» – там больше нет числа 2000, как и всех предыдущих, которые меньше.
Решение - IDE

print(next(numbers))
Результат выполнения

2001

Задача 2. Базовый уровень

Условие

Создайте функцию infinite(lst, tries), которая будет проходиться по элементам списка lst (целые числа) заданное количество раз (tries) циклически. 
Один раз - один элемент списка. 
После вывода последнего значения последовательности процедура начнется с самого начала.

Например, если в списке 2 элемента, а функция получила значение 3, то сначала выведется первый объект, потом последний, а потом опять первый. 
Результат работы функции представьте в виде строки, состоящей из tries количества символов.
Для решения задачи нужно использовать функцию cycle() из модуля itertools. Она перебирает последовательность циклически, а по мере достижения последнего элемента начинает заново.
Решение - IDE

from itertools import cycle
 
 
def infinite(lst, iterations):
    result = ''
    iter_lst = cycle(lst)
    if lst:
        for symbol in range(iterations):
            result += str(next(iter_lst))
    return result
 
 
# Тесты
print(infinite([2, 5, 8], 7))
print(infinite([], 1000))
print(infinite([7], 4))
Результат выполнения

2582582
# Пустая строка
7777

Задача 3. Базовый уровень

Условие

Инструкция yield позволяет создавать генераторы. 
В отличие от объявления return в функции, где возвращается один объект, yield при каждом вызове функции генерирует новый объект. 
Фактически это дает возможность использовать генераторы в циклах. 
Самая важная причина применения такой инструкции - экономия памяти, когда не требуется сохранять всю последовательность, а можно получать ее элементы по одному. 

Ученик написал генератор show_letters(some_str), выводящий все символы строки на печать, но только в том случае, если они являются буквами (остальные игнорируются). 
Сократите код функции.
 
Код – IDE
---
def show_letters(some_str):
    clean_str = ''.join([letter for letter in some_str if letter.isalpha()])
    for symbol in clean_str:
        yield symbol
 
Конструкция yield from позволяет полностью убрать цикл из функции. Она "вкладывает" один генератор внутрь другого, что дает возможность управления несколькими генераторами.
Решение - IDE

def show_letters(some_str):
	yield from ''.join([letter for letter in some_str if letter.isalpha()])
 
 
random_str = show_letters('A!sdf 09 _ w')
print(next(random_str))
print(next(random_str))
Результат выполнения

A
s
 

Задача 4. Продвинутый уровень

Условие

Числа Фибоначчи представляют последовательность, получаемую в результате сложения двух предыдущих элементов. 
Начинается коллекция с чисел 1 и 1. 
Она достаточно быстро растет, поэтому вычисление больших значений занимает немало времени. 
Создайте функцию fib(n), генерирующую n чисел Фибоначчи с минимальными затратами ресурсов.
Для реализации этой функции потребуется обратиться к инструкции yield. 
Она не сохраняет в оперативной памяти огромную последовательность, а дает возможность “доставать” промежуточные результаты по одному. 
 
Необходимо превратить функцию в генератор при помощи инструкции yield, чтобы вычисления осуществлялись не сразу, а по мере надобности.
Решение - IDE

def fib(n):
    fib0 = 1
    yield fib0
    fib1 = 1
    yield fib1
    for i in range(n - 2):
        fib0, fib1 = fib1, fib0 + fib1
        yield fib1
 
 
# Тест
for num in fib(112121):
	pass
print(num)
Результат выполнения

3578717901141606491845…

Задача 5. Продвинутый уровень

Условие

Реализуйте итератор колоды карт (52 штуки) CardDeck. Каждая карта представлена в виде строки типа «2 Пик». При вызове функции next() будет представлена следующая карта. По окончании перебора всех элементов возникнет ошибка StopIteration.
Чтобы реализовать протокол итератора требуется внедрить 2 метода: __iter__() и __next__(). Для понимания того, что коллекция иссякла, не стоить забывать и про ее длину (52).
Решение - IDE

class CardDeck:
    def __init__(self):
        self.length = 52
        self.index = 0
        self.__SUITS = ['Пик', 'Бубей', 'Червей', 'Крестей']
        self.__RANKS = [*range(2, 11), 'J', 'Q', 'K', 'A']

    def __len__(self):
        return self.length

    def __next__(self):
        if self.index >= self.length:
            raise StopIteration
        else:
            suit = self.__SUITS[self.index // len(self.__RANKS)]
            rank = self.__RANKS[self.index % len(self.__RANKS)]
            self.index += 1
            return f'{rank} {suit}'

    def __iter__(self):
        return self


deck = CardDeck()
while True:
    print(next(deck))
 
Результат выполнения

2 Пик
3 Пик
4 Пик
…
K Крестей
A Крестей
StopIteration
Как вам материал?

Читайте также