Итератор 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
надеюсь, что хватит информации, спасибо
изменить:
Я просто хочу, чтобы иметь возможность перебирать все листья дерева и делать что-то с объектом, то есть когда у меня есть экземпляр
bla = t(3)
Я хочу иметь возможность проходить через node с помощью
for x in bla:
print x.a
например. я хочу иметь возможность чего-то с каждым х,
я просто должен получить доступ к каждому ребенку один раз
Ответы
Ответ 1
Похоже, вы хотите, чтобы итератор действовал как обход дерева. Изучите модуль 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)]))
Ответ 2
Из кода, который вы опубликовали, ясно, что то, что вам не хватает, это то, что делает генератор, и как __iter__
и next
должны вести себя
Итак, давайте начнем с протокола итератора. объект итерабельен, если он возвращает итератор, когда вызывается его метод __iter__
, а итератор - это объект, который имеет метод next
, который может быть вызван ноль или более раз и в конечном итоге должен поднять StopIteration
.
Не для некоторых видов объектов не характерны их собственные итераторы (которые имеют __iter__
return self
), но обычно это ограничивается объектами, которые каким-то образом сами представляют собой позицию внутри чего-то. Например, встроенный объект file
является его собственным итератором, потому что файлы имеют внутреннюю позицию поиска (с которыми вы можете манипулировать с помощью file.seek()
и file.tell()
). Другие объекты, которые представляют совокупность коллекции, например list
, возвращают нечто иное, чем они сами.
Итак, ваше дерево действительно больше похоже на последнее, а не на первое; У него нет атрибута позиции, который представляет, с какими node он включен; все узлы одновременно, поэтому он, вероятно, не должен иметь метод 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 multiple children, and we need to iterate
# over each one.
yield item
# finally, yield self
yield self
Но, поскольку то, что мы делаем, это итерация последовательности итераторов (а также еще одна вещь, я), itertools.chain
, как в принятом ответе, действительно имеет большой смысл.
Ответ 3
Мое первое предложение состоит в том, чтобы изменить название вашего класса с более четким, следуя 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()
вызывается на листе node (a node без детей), он просто вернет сам node. OTOH, когда вызывается с node с детьми, он будет называть себя для каждого ребенка и возвращает возвращаемое значение. Это означает, что он будет вызван детьми детей и т.д. До тех пор, пока узлы листьев не будут. После вызова всех потомков a node, что означает, что все потомки были уступлены, он должен дать свое значение, поэтому вы должны получить исходный node.
Теперь ваша функция printall()
должна работать безупречно:
if __name__ == "__main__":
t = Tree(6)
t.printall()
Некоторые окончательные соображения:
-
Всегда делайте, чтобы ваши классы расширяли object
:
класс Дерево (объект)::
-
Бьюсь об заклад, вы хотите написать метод __init__()
, подобный приведенному ниже:
def __init__(self, i):
self.l = []
self.a = i
for ii in range(i):
self.l.append(Tree(i-1))
-
Решение wberry лучше, потому что оно более кратким и, вероятно, более эффективным. Тем не менее, я думаю, что OP изучает деревья, рекурсию и т.д., Поэтому я думал, что более жесткое кодирование будет поучительным:)