Подтвердить что ты не робот

Индекс и фрагмент генератора в Python

Допустим, у меня есть функция генератора, которая выглядит так:

def fib():
    x,y = 1,1
    while True:
        x, y = y, x+y
        yield x

В идеале я мог бы просто использовать fib() [10] или fib() [2: 12: 2], чтобы получить индексы и срезы, но в настоящее время я должен использовать itertools для этих вещей. Я не могу использовать генератор для замены в списках.

Я считаю, что решение будет состоять в том, чтобы wrap fib() в классе:

class Indexable(object):
    ....

fib_seq = Indexable(fib())

Что должно выглядеть с индексом, чтобы сделать эту работу?

4b9b3361

Ответ 1

import itertools

class Indexable(object):
    def __init__(self,it):
        self.it = iter(it)
    def __iter__(self):
        return self.it
    def __getitem__(self,index):
        try:
            return next(itertools.islice(self.it,index,index+1))
        except TypeError:
            return list(itertools.islice(self.it,index.start,index.stop,index.step))

Вы можете использовать его следующим образом:

it = Indexable(fib())
print(it[10])
#144
print(it[2:12:2])
#[610, 1597, 4181, 10946, 28657]

Обратите внимание, что it[2:12:2] не возвращает [3, 8, 21, 55, 144], так как итератор уже продвинул 11 элементов из-за вызова it[10].

Изменить: Если вы хотите it[2:12:2] вернуть [3, 8, 21, 55, 144], то, возможно, используйте это вместо:

class Indexable(object):

    def __init__(self, it):
        self.it = iter(it)
        self.already_computed = []

    def __iter__(self):
        for elt in self.it:
            self.already_computed.append(elt)
            yield elt

    def __getitem__(self, index):
        try:
            max_idx = index.stop
        except AttributeError:
            max_idx = index
        n = max_idx - len(self.already_computed) + 1
        if n > 0:
            self.already_computed.extend(itertools.islice(self.it, n))
        return self.already_computed[index]

Эта версия сохраняет результаты в self.already_computed и использует эти результаты если возможно. В противном случае он вычисляет больше результатов, пока не будет достаточно много для возврата индексированного элемента или среза.

Ответ 2

Таким образом, основываясь на коде от ~ unutbu и добавляя немного itertools.tee:

import itertools

class Indexable(object):
    def __init__(self, it):
        self.it = it

    def __iter__(self):
        self.it, cpy = itertools.tee(self.it)
        return cpy

    def __getitem__(self, index):
        self.it, cpy = itertools.tee(self.it)
        if type(index) is slice:
            return list(itertools.islice(cpy, index.start, index.stop, index.step))
        else:
            return next(itertools.islice(cpy, index, index+1))

Ответ 3

Если это 1-секционный фрагмент, вы можете просто использовать метод, написанный ~ unutbu. Если вам нужно срезать несколько раз, вам нужно будет сохранить все промежуточные значения, чтобы вы могли "перемотать" итератор. Поскольку итераторы могут повторять все, что по умолчанию не будет иметь метод перемотки.

Кроме того, поскольку перематывающий итератор должен хранить каждый промежуточный результат, он (в большинстве случаев) не имеет преимущества, просто выполняя list(iterator)

В принципе... вам либо не нужен итератор, либо вы недостаточно конкретны в этой ситуации.

Ответ 4

Здесь ~ unutbu answer изменен в список подкласса. Очевидно, что такие злоупотребления, как append, insert и т.д., Приведут к странным результатам!

вы получаете бесплатные методы __str__ и __repr__, хотя

import itertools
class Indexable(list):
    def __init__(self,it):
        self.it=it
    def __iter__(self):
        for elt in self.it:
            yield elt
    def __getitem__(self,index):
        try:
            max_idx=index.stop
        except AttributeError:
            max_idx=index
        while max_idx>=len(self):
            self.append(next(self.it))
        return list.__getitem__(self,index)

Ответ 5

Для islice генератора вы можете использовать функцию itertools из itertools

from itertools import islice

for i in islice(generator, 5):
    # Will be taken first 5 elems

for i in islice(generator, 5, None):
    # Will be taken everything starting at 5th