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