Как написать итератор простых чисел
class LowerPrime:
def __init__(self, number):
self.number = number
def __iter__(self):
self.it = 0
return self
def __next__(self):
if self.it == 2:
raise StopIteration
num = []
for n in range(self.number):
for i in range(2, n):
if n % i == 0:
break
else:
num.append(n)
print(num)
lower_prime = LowerPrime(number=11)
lower_prime_it = iter(lower_prime)
next(lower_prime_it)
Задача состоит в следующем. На входе есть например число 11. При каждом вызове next выводится меньшее простое число 7 следующий next выводит 5 и так далее. Как только достигли 2 выводим ошибку Stopiteration. Обязательное условие задачи использовать написанный в ручную итератор и вызов должен выглядеть так как в примере. В моем понимаеии нужно создать список простых чисел по убыванию и при каждом вызове через next выводить по одному числу. Вот так :
next(lower_prime_it) == 7
next(lower_prime_it) == 5
Получилось создать список с простыми числами но не получается вывести их по очередно через next. Помогите пожалуйста разобраться что не так
Ответы (2 шт):
У задачи есть две стороны: математическая и технологическая. Математическую я предельно упростил - простота числа проверяется по определению. Можно сделать быстрее, но пока так.
Технологическая сторона. Я сделаю LowerPrime аналогом range. В Питоне range - неизменяемая последовательность. Из последовательности можно получить сколько угодно итераторов. Все итераторы полностью независимы. Демонстрация:
@>>> r = range(10) @>>> r range(0, 10) @>>> it = iter(r) @>>> it <range_iterator object at 0x7fc241c2b630> @>>> it2 = iter(r) @>>> it2 <range_iterator object at 0x7fc241c2a7f0>
Схема "одна последовательность - много итераторов" нужна чтобы работал код вроде:
r = range(10)
for n in r: # for вызывает iter(r) в начале
# что-то делаем
for n in r: # for вызывает iter(r) в начале
# что-то делаем
Без разных итераторов второй цикл не выполнится, так как первый исчерпал свой итератор полностью и нам нужен новый итератор.
Тоже самое для списка. Список один, итераторов много:
@>>> lst = [1, 2, 3] @>>> it = iter(lst) @>>> it <list_iterator object at 0x7fc241d12710> @>>> it2 = iter(lst) @>>> it2 <list_iterator object at 0x7fc241c781c0>
Становится ясно, что нужны два класса. Для списка убывающих простых LowerPrime, для итераторов LowerPrimeIter. Список простых заранее можно не составлять. Каждый вызов next продолжает поиск простых с того места, где итератор остановился раньше:
def is_prime(n):
return all(n % i != 0 for i in range(2, n))
class LowerPrime:
def __init__(self, number):
self._number = number
def __iter__(self):
return LowerPrimeIter(self._number)
class LowerPrimeIter:
def __init__(self, n):
self._n = n + 1
def __iter__(self):
return self
def __next__(self):
if self._n <= 2:
raise StopIteration
while True:
self._n -= 1
if is_prime(self._n):
return self._n
lower_prime = LowerPrime(number=11)
lower_prime_it = iter(lower_prime)
print('first', next(lower_prime_it))
for p in lower_prime_it:
print(p)
$ python lower_prime.py first 11 7 5 3 2
Так то и ваш код можно переделать под работающий с минимальными изменениями:
class LowerPrime:
def __init__(self, number):
self.number = number
def __iter__(self):
self.it = self.number
return self
def __next__(self):
if self.it < 2:
raise StopIteration
num = []
for n in range(self.it, 1, -1):
for i in range(2, n):
if n % i == 0:
break
else:
self.it = n - 1
return n
lower_prime = LowerPrime(number=11)
for i in lower_prime:
print(i)
for i in lower_prime:
print(i)
Вывод:
11
7
5
3
2
11
7
5
3
2
Но в некоторых случаях мой код будет работать недостаточно правильно, например, если нужно параллельно итерироваться по двум итераторам от одного объекта LowerPrime:
print(list(zip(lower_prime, lower_prime)))
# [(11,), (7,), (5,), (3,), (2,)]
В этом случае подойдёт ответ Stanislav Volodarskiy с отдельным объектом-итератором.