Use the node built-in 'dfs_iter' instead of recursion

We can just use the non-recursive depth first iteration
of nodes when scanning for atoms to select for a given
scope level instead of using recursive calls to achieve
the same effect.

This makes it possible to have large and heavily nested
flows that are not restricted by the python stack limit.

Change-Id: I0d18565680f777adbdfca9d4983636c6b3e848da
This commit is contained in:
Joshua Harlow
2015-07-24 18:10:55 -07:00
parent 79d25e69e8
commit 75517eb0e0
3 changed files with 62 additions and 28 deletions

View File

@@ -21,7 +21,11 @@ LOG = logging.getLogger(__name__)
def _depth_first_reverse_iterate(node, idx=-1):
"""Iterates connected (in reverse) nodes in tree (from starting node)."""
"""Iterates connected (in reverse) nodes (from starting node).
Jumps through nodes with ``FLOW`` ``kind`` attribute (does not yield
them back).
"""
# Always go left to right, since right to left is the pattern order
# and we want to go backwards and not forwards through that ordering...
if idx == -1:
@@ -29,15 +33,11 @@ def _depth_first_reverse_iterate(node, idx=-1):
else:
children_iter = reversed(node[0:idx])
for child in children_iter:
child_kind = child.metadata['kind']
if child_kind == co.FLOW:
if child.metadata['kind'] == co.FLOW:
# Jump through these...
#
# TODO(harlowja): make this non-recursive and remove this
# style of doing this when
# https://review.openstack.org/#/c/205731/ merges...
for atom in _depth_first_reverse_iterate(child):
yield atom
for child_child in child.dfs_iter(right_to_left=False):
if child_child.metadata['kind'] in co.ATOMS:
yield child_child.item
else:
yield child.item

View File

@@ -467,24 +467,38 @@ CEO
self.assertEqual(set(['animal', 'reptile', 'mammal', 'horse',
'primate', 'monkey', 'human']), set(things))
def test_dfs_itr_order(self):
def test_dfs_itr_left_to_right(self):
root = self._make_species()
it = root.dfs_iter(include_self=False, right_to_left=False)
things = list([n.item for n in it])
self.assertEqual(['reptile', 'mammal', 'primate',
'human', 'monkey', 'horse'], things)
def test_dfs_itr_no_self(self):
root = self._make_species()
things = list([n.item for n in root.dfs_iter(include_self=True)])
self.assertEqual(['animal', 'mammal', 'horse', 'primate',
'monkey', 'human', 'reptile'], things)
things = list([n.item for n in root.dfs_iter(include_self=False)])
self.assertEqual(['mammal', 'horse', 'primate',
'monkey', 'human', 'reptile'], things)
def test_bfs_iter(self):
def test_bfs_itr(self):
root = self._make_species()
things = list([n.item for n in root.bfs_iter(include_self=True)])
self.assertEqual(['animal', 'reptile', 'mammal', 'primate',
'horse', 'human', 'monkey'], things)
def test_bfs_itr_no_self(self):
root = self._make_species()
things = list([n.item for n in root.bfs_iter(include_self=False)])
self.assertEqual(['reptile', 'mammal', 'primate',
'horse', 'human', 'monkey'], things)
def test_bfs_itr_right_to_left(self):
root = self._make_species()
it = root.bfs_iter(include_self=False, right_to_left=True)
things = list([n.item for n in it])
self.assertEqual(['mammal', 'reptile', 'horse',
'primate', 'monkey', 'human'], things)
class OrderedSetTest(test.TestCase):

View File

@@ -36,8 +36,9 @@ class FrozenNode(Exception):
class _DFSIter(object):
"""Depth first iterator (non-recursive) over the child nodes."""
def __init__(self, root, include_self=False):
def __init__(self, root, include_self=False, right_to_left=True):
self.root = root
self.right_to_left = bool(right_to_left)
self.include_self = bool(include_self)
def __iter__(self):
@@ -45,20 +46,28 @@ class _DFSIter(object):
if self.include_self:
stack.append(self.root)
else:
if self.right_to_left:
stack.extend(self.root.reverse_iter())
else:
# Traverse the left nodes first to the right nodes.
stack.extend(iter(self.root))
while stack:
node = stack.pop()
# Visit the node.
node = stack.pop()
yield node
# Traverse the left & right subtree.
if self.right_to_left:
stack.extend(node.reverse_iter())
else:
# Traverse the left nodes first to the right nodes.
stack.extend(iter(node))
class _BFSIter(object):
"""Breadth first iterator (non-recursive) over the child nodes."""
def __init__(self, root, include_self=False):
def __init__(self, root, include_self=False, right_to_left=False):
self.root = root
self.right_to_left = bool(right_to_left)
self.include_self = bool(include_self)
def __iter__(self):
@@ -66,12 +75,19 @@ class _BFSIter(object):
if self.include_self:
q.append(self.root)
else:
if self.right_to_left:
q.extend(iter(self.root))
else:
# Traverse the left nodes first to the right nodes.
q.extend(self.root.reverse_iter())
while q:
node = q.popleft()
# Visit the node.
node = q.popleft()
yield node
# Traverse the left & right subtree.
if self.right_to_left:
q.extend(iter(node))
else:
# Traverse the left nodes first to the right nodes.
q.extend(node.reverse_iter())
@@ -361,10 +377,14 @@ class Node(object):
raise ValueError("%s is not contained in any child" % (item))
return index_at
def dfs_iter(self, include_self=False):
def dfs_iter(self, include_self=False, right_to_left=True):
"""Depth first iteration (non-recursive) over the child nodes."""
return _DFSIter(self, include_self=include_self)
return _DFSIter(self,
include_self=include_self,
right_to_left=right_to_left)
def bfs_iter(self, include_self=False):
def bfs_iter(self, include_self=False, right_to_left=False):
"""Breadth first iteration (non-recursive) over the child nodes."""
return _BFSIter(self, include_self=include_self)
return _BFSIter(self,
include_self=include_self,
right_to_left=right_to_left)