Как написать итератор простых чисел

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 шт):

Автор решения: Stanislav Volodarskiy

У задачи есть две стороны: математическая и технологическая. Математическую я предельно упростил - простота числа проверяется по определению. Можно сделать быстрее, но пока так.

Технологическая сторона. Я сделаю 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
→ Ссылка
Автор решения: CrazyElf

Так то и ваш код можно переделать под работающий с минимальными изменениями:

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 с отдельным объектом-итератором.

→ Ссылка