Индексировать и разрезать генератор в Python - PullRequest
28 голосов
/ 24 февраля 2010

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

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

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

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

class Indexable(object):
    ....

fib_seq = Indexable(fib())

Как должен выглядеть Indexable , чтобы эта работа работала?

Ответы [ 5 ]

33 голосов
/ 24 февраля 2010
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 и использует эти результаты если возможно. В противном случае он вычисляет больше результатов, пока не получит достаточно много вернуть индексированный элемент или фрагмент.

0 голосов
/ 15 мая 2019

Для генератора срезов вы можете использовать функцию islice из 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
0 голосов
/ 24 февраля 2010

Вот ответ ~ unutbu, модифицированный для списка подклассов. Очевидно, что злоупотребление, такое как 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)
0 голосов
/ 24 февраля 2010

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

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

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

0 голосов
/ 24 февраля 2010

Итак, основываясь на коде ~ 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))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...