Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide an iterative version on some functions on trees #39519

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
190 changes: 174 additions & 16 deletions src/sage/combinat/abstract_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,7 @@
while stack:
node = stack.pop()
action(node)
for i in range(len(node)):
subtree = node[-i - 1]
for subtree in reversed(node):
if not subtree.is_empty():
stack.append(subtree)

Expand Down Expand Up @@ -639,15 +638,127 @@
# subtrees, and should not be exploded again, but instead
# should be manipulated and removed from the stack.
stack.append(None)
for i in range(len(node)):
subtree = node[-i - 1]
for subtree in reversed(node):
if not subtree.is_empty():
stack.append(subtree)
else:
stack.pop()
node = stack.pop()
action(node)

def contour_traversal(self,first_action=None, middle_action=None, final_action=None, leaf_action=[]):
Copy link
Contributor

@mantepse mantepse Feb 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def contour_traversal(self,first_action=None, middle_action=None, final_action=None, leaf_action=[]):
def contour_traversal(self, first_action=None, middle_action=None, final_action=None, leaf_action=None):

I don't see why leaf_action should not be None by default.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want that if you change only first_action and final_action, leaf_action will be the action of both of them but it's perhaps a bad idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand, can you give an example?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look at the depth code : i only change the final_action and it change accordingly the leaf_action.
I would like it to work like that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think again at this point and I think you are right, it's probably better that the default leaf_action is None.
I made the change in the last commit.

r"""
Run the counterclockwise countour traversal algorithm (iterative
implementation) and subject every node encountered
to some procedure ``first_action``, ``middle_action`` or ``final_action`` each time it reaches it.
The algorithm is::

manipulate the root with function `first_action`;
iteratively manipulate the root with function `middle_action`
and explore each subtree (by the algorithm) from the
leftmost one to the rightmost one;
then manipulate the root with function `final_action`;
if the root is a leaf it only manipulate with function `leaf_action`.

INPUT:

- ``first_action`` -- (optional) a function which takes a node as
input, and does something the first time it is reached during exploration

- ``middle_action`` -- (optional) a function which takes a node as
input, and does something each time it explore one of its children

- ``last_action`` -- (optional) a function which takes a node as
input, and does something the last time it is reached during exploration

- ``leaf_action`` -- (optional) a function which takes a leaf as
input, and does something when it is reached during exploration.
By default, `leaf_action` is the same as `first_action` and `last_action`

OUTPUT:

``None``. (This is *not* an iterator.)

TESTS::

sage: l = []
sage: t = OrderedTree([[],[[],[],]]).canonical_labelling()
sage: t
1[2[], 3[4[], 5[]]]
sage: t.contour_traversal(lambda node: (l.append(node.label()),l.append('a')),
....: lambda node: (l.append(node.label()),l.append('b')),
....: lambda node: (l.append(node.label()),l.append('c')),
....: lambda node: (l.append(node.label())))
sage: l

Check failure on line 692 in src/sage/combinat/abstract_tree.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: [1, 'a', 1, 'b', 2, 1, 'b', 3, 'a', 3, 'b', 4, 3, 'b', 5, 3, 'c', 1, 'c']
sage: [1, 'a', 1, 'b', 2, 1, 'b', 3, 'a', 3, 'b', 4, 3, 'b', 5, 3, 'c', 1, 'c']

Check failure on line 693 in src/sage/combinat/abstract_tree.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: [1, 'a', 1, 'b', 2, 1, 'b', 3, 'a', 3, 'b', 4, 3, 'b', 5, 3, 'c', 1, 'c']

sage: l = []
sage: b = BinaryTree([[None,[]],[[[],[]],[]]]).canonical_labelling()
sage: b
3[1[., 2[., .]], 7[5[4[., .], 6[., .]], 8[., .]]]
sage: b.contour_traversal(lambda node: l.append(node.label()),
....: lambda node: l.append(node.label()),
....: lambda node: l.append(node.label()),
....: None)
sage: l
[3, 3, 1, 1, 1, 2, 2, 2, 2, 1, 3, 7, 7, 5, 5, 4, 4, 4, 4, 5, 6, 6, 6, 6, 5, 7, 8, 8, 8, 8, 7, 3]

The following test checks that things do not go wrong if some among
the descendants of the tree are equal or even identical::

sage: u = BinaryTree(None)
sage: v = BinaryTree([u, u])
sage: w = BinaryTree([v, v])
sage: t = BinaryTree([w, w])
sage: t.node_number()
7
sage: l = []
sage: t.contour_traversal(first_action = lambda node: l.append(0),leaf_action = None)
sage: len(l)
7

"""
if first_action is None:
def first_action(x):
return None
if middle_action is None:
def middle_action(x):
return None
if final_action is None:
def final_action(x):
return None
if leaf_action is None:
def leaf_action(x):
None
elif leaf_action == []:
def leaf_action(x):
first_action(x)
final_action(x)
stack = []
stack.append(self)
corners = [0,0]
while stack:
node = stack.pop()
if not(bool(node)):
leaf_action(node)
corners.pop()
corners[-1] += 1
elif corners[-1] == 0:
first_action(node)
middle_action(node)
stack.append(node)
stack.append(node[0])
corners.append(0)
elif corners[-1] == len(node):
final_action(node)
corners.pop()
corners[-1] += 1
else:
middle_action(node)
stack.append(node)
stack.append(node[corners[-1]])
corners.append(0)

def breadth_first_order_traversal(self, action=None):
r"""
Run the breadth-first post-order traversal algorithm
Expand Down Expand Up @@ -823,12 +934,28 @@
True
sage: [T.node_number_at_depth(i) for i in range(3)]
[0, 0, 0]

Check that we do not hit a recursion limit::

sage: T = OrderedTree([])
sage: for _ in range(9999):
....: T = OrderedTree([T])
sage: T.node_number_at_depth(2000)
1
"""
if self.is_empty():
return Integer(0)
if depth == 0:
return Integer(1)
return sum(son.node_number_at_depth(depth - 1) for son in self)
return 0
def fr_action(node, depths, m, depth):
if depths[-1] == depth:
m[0] += 1
def m_action(node,depths):
depths.append(depths[-1]+1)
def fn_action(node,depths):
depths.pop()
depths = [0]
m = [0]
self.contour_traversal(lambda node: fr_action(node, depths,m, depth),lambda node: m_action(node, depths),lambda node: fn_action(node, depths))
return m[0]

def paths_to_the_right(self, path):
r"""
Expand Down Expand Up @@ -1052,11 +1179,23 @@
2
sage: BinaryTree([[None, [[], []]], None]).node_number()
5

TESTS::

Check that we do not hit a recursion limit::

sage: T = OrderedTree([])
sage: for _ in range(9999):
....: T = OrderedTree([T])
sage: T.node_number()
10000
"""
if self.is_empty():
return Integer(0)
else:
return sum((i.node_number() for i in self), Integer(1))
count = 0
def incr(node):
nonlocal count
count += 1
self.iterative_pre_order_traversal(incr)
return Integer(count)

def depth(self):
"""
Expand All @@ -1079,11 +1218,30 @@
0
sage: BinaryTree([[],[[],[]]]).depth()
3

TESTS::

Check that we do not hit a recursion limit::

sage: T = OrderedTree([])
sage: for _ in range(9999):
....: T = OrderedTree([T])
sage: T.depth()
10000
"""
if self:
return Integer(1 + max(i.depth() for i in self))
else:
return Integer(0 if self.is_empty() else 1)
if self.is_empty():
return 0
def action(node,m):
if node.is_empty():
m.append(-1)
elif not(bool(node)):
m.append(0)
else:
mx = max(m.pop() for _ in node)
m.append(mx+1)
m = []
self.contour_traversal(final_action = lambda node: action(node,m))
return m[0]+1

def _ascii_art_(self):
r"""
Expand Down
Loading