итератор python через дерево со списком детей

Я не полностью понимаю итераторы python, у меня есть объект со списком детей, и я хочу перебрать эту структуру. Я хочу получить то же поведение, что и с функцией printall, но с итератором.

    class t:
            def __init__(self, i):
                    self.l = []
                    self.a = 0
                    for ii in range(i):
                            self.a = ii
                            self.l.append(t(i-1))

            def __iter__(self):
                    return self

            def next(self):
                    for i in self.l:
                            yield i.__iter__()
                    yield self

            def printall(self):
                    for i in self.l:
                            i.printall()
                    print self.a

надеюсь, что этого достаточно, спасибо

edit:

Я просто хочу иметь возможность перебирать все листья дерева и делать что-то с объектом, т. е. когда у меня есть экземпляр

    bla = t(3) 

i хотите иметь возможность проходить через каждый узел с

    for x in bla:
            print x.a

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

3 ответов


похоже, вы хотите, чтобы итератор действовал как обход дерева. Изучите itertools модуль, и вы действительно можете пойти в места.

from itertools import chain, imap

class t:
  def __init__(self, value):
    self.value = value
    self.children = []
  def __iter__(self):
    "implement the iterator protocol"
    for v in chain(*imap(iter, self.children)):
      yield v
    yield self.value

root = t(0)
root.children.append(t(1))
root.children.append(t(2))
root.children[1].children.append(t(3))
print list(iter(root))   # -> [1, 3, 2, 0]
print list(iter(root.children[1]))  # -> [3, 2]

редактировать: ниже приведена первоначально принятая реализация. У этого есть проблема с производительностью; я бы удалил его, но кажется неправильным удалять контент, который был принятым ответом. Он будет полностью пересекать всю структуру, создавая O(N*log[M](N)) объекты генератора (для сбалансированного дерева с коэффициентом ветвления M содержащий N total elements), прежде чем давать какие-либо значения. Но он дает желаемый результат с помощью простого выражения.

(вышеуказанная реализация посещает области дерева по требованию и имеет только O(M+log[M](N)) объекты генератора в памяти. В обеих реализациях только O(log[M](N)) ожидаются уровни вложенных генераторов.)

from itertools import chain

def isingle(item):
  "iterator that yields only a single value then stops, for chaining"
  yield item

class t:
  # copy __init__ from above
  def __iter__(self):
    "implement the iterator protocol"
    return chain(*(map(iter, self.children) + [isingle(self.value)]))

из кода, который вы опубликовали, ясно, что вам не хватает что делает генератор и как __iter__ и next должны вести себя

Итак, давайте начнем с протокола итератора. объект является итерационным, если он возвращает итератор, когда его __iter__ вызывается метод, а итератор-это объект, который имеет next метод, который можно назвать нулевым или более раз и должен в конечном итоге поднять StopIteration.

это не необычно для некоторых видов объектов, чтобы быть их собственные итераторы (которые __iter__ return self), но это обычно ограничивается объектами, которые каким-то образом сами представляют позицию внутри чего-то. Например, строение file объект является собственным итератором, потому что файлы имеют внутреннюю позицию поиска (которой вы можете управлять с помощью file.seek() и file.tell()). Другие объекты, которые представляют собой совокупность коллекции, например list, вернуть что-то другие, чем они сами.

Итак, ваше дерево действительно больше похоже на последнее, а не на первое; у него нет атрибута позиции, который представляет, на каком узле он находится; это все узлы одновременно, поэтому, вероятно,не стоит есть next() способ; __iter__ нужно вернуть что-то еще.

что приводит нас к генераторам. Когда нормальная функция содержит yield заявление, это автоматически не функция вообще, это генератор. Разница в том, что когда вы вызываете функцию, ее тело выполняется (и, возможно, возвращает значение). Когда вы вызываете генератор, он возвращается немедленно, не выполняя тело вообще; вместо этого вы получаете итератор! когда вы повторяете это, тело функции вызывается; переход к следующему yield каждый раз, пока он, наконец, не вернется.

Итак, собирая все вместе,

class t:
    def __init__(self):
        self.l = []
        self.a = 0

    def __iter__(self):
        # first, yield everthing every one of the child nodes would yield.
        for child in self.l:
            for item in child:
                # the two for loops is because there's multiple children, and we need to iterate
                # over each one.
                yield item

        # finally, yield self
        yield self

но, поскольку то, что мы делаем, повторяя последовательность итераторы (а также еще одна вещь, самовыдвижение), itertools.chain как и в принятом ответе, действительно имеет смысл.


мое первое предложение состоит в том, чтобы изменить название вашего класса с более ясным после PEP-8. Было немного сложно управлять именем класса, таким как t:

class Tree:
    def __init__(self, i):
        self.l = []
        self.a = 0
        for ii in range(i):
            self.a = ii
            self.l.append(Tree(i-1))

Итак, вы должны изменить __iter__() метод для возврата следующего элемента в self, а не self сама-не каламбур:) то __iter__() метод должен возвращать итератор исходному объекту, а не сам объект:

def __iter__(self):
    return next(self)

теперь приходит трудная часть: next() метод. Мне всегда трудно написать рекурсивные итераторы, но это не так уж невозможно: для каждого ребенка повторите его и получите итерационное значение:

def next(self):
    for i in self.l:
        for ii in i:
            yield ii
    yield self

поскольку метод рекурсивен, он заботится о том, чтобы дать все потомки. Когда next() метод вызывается на листовом узле (узле без потомков), он просто вернет сам узел. OTOH, когда вызывается узел с дочерними элементами, он будет вызывать себя для каждого дочернего элемента и давать возвращаемое значение. Это означает, что он будет вызываться детьми детей и так далее до листовых узлов. После вызова все потомки узла-это означает, что все потомки были получены -, он должен дать свое собственное значение, поэтому вы должны дать исходный узел сам.

теперь ваш