How yield works in Inorder traversal with recursive generator? - python-3.x

I am learning about recursive generators and found this program on web.
I undertand the recursive version of In-Order traversal but i am having trouble understanding recursive generator.
Specifically i am not able to understand
why 'yield x' is written in for loop?
Why 'yield x' is not collected in final list?
I have tried to dubug the generator and added the watch but i found that recursive call execute multiple time 'yield x' and it's not collected in final result.
class Tree:
def __init__(self, label, left=None, right=None):
self.label = label
self.left = left
self.right = right
def __repr__(self, level=0, indent=" "):
s = level*indent + repr(self.label)
if self.left:
s = s + "\\n" + self.left.__repr__(level+1, indent)
if self.right:
s = s + "\\n" + self.right.__repr__(level+1, indent)
return s
def __iter__(self):
return inorder(self)
def tree(list):
n = len(list)
if n == 0:
return []
i = n // 2
return Tree(list[i], tree(list[:i]), tree(list[i + 1:]))
# Recursive Generator
def inorder(t):
if t:
for x in inorder(t.left):
yield x
yield t.label
for x in inorder(t.right):
yield x
Role of yield x inside for loop.

Maybe what you don't understand is how the generator works? The generator is different from the iterator, it is not directly calculating the worthy collection, it is dynamically getting all the values. If the result of inorder(t.left) is not traversed by a for loop, then yield inorder(t.left) will return the entire generator created for t.left. Because your entire inorder function is a generator.
Unfortunately, I have not been able to find a specific description document. This is a description based on my experience. Others are welcome to make corrections to my opinion. If someone can provide a specific official description, welcome to add

Not sure exactly what you're looking for, but perhaps it is something along these lines:
class Tree:
def __init__(self, label, left=None, right=None):
self.label = label
self.left = left
self.right = right
def __repr__(self, level=0, indent=" "):
s = level*indent + repr(self.label)
if self.left:
s = s + "\n" + self.left.__repr__(level+1, indent)
if self.right:
s = s + "\n" + self.right.__repr__(level+1, indent)
return s
def __iter__(self):
return inorder(self)
def tree(list):
n = len(list)
if n == 0:
return []
i = n // 2
return Tree(list[i], tree(list[:i]), tree(list[i + 1:]))
def inorder(t):
if t:
for x in t.left:
yield x
yield t.label
for x in t.right:
yield x
t = tree("ABCDEFG")
[print(i.label) for i in t]
which outputs:
A
B
C
D
E
F
G
With that code you could instead:
[print('----------\n', i) for i in t]
which outputs each hierarchical node in the tree, from A to G.
Edit: If you're asking how generator works, perhaps this example could be enlightening:
>>> def list2gen(lst):
... for elem in lst:
... yield str(elem) + '_'
...
>>> print(list2gen([1,2,'7',-4]))
<generator object list2gen at 0x000001B0DEF8B0C0>
>>> print(list(list2gen([1,2,'7',-4])))
['1_', '2_', '7_', '-4_']
If your debugger breaks multiple times at a yield, but those elements never materialize in the resulting generator, you'd have to attribute that to bugs in the debugger. I've not used one for Python in more than a decade; they used to be notoriously bug-infested. In Python the paradigm is "test is king," and "avoid manual debugging," but I disagree with that. (The only reason I don't is lack of great IDE's and debuggers.)

Related

How to sort a list into even then odd numbers using recursion when the function can only call itself and use basic list manipulations

The function takes in a list of integers and returns a list of the same elements sorted into the order of even integers then odd ones. Im struggling with implementing a recursive function to complete it even though I am able to solve this using a for loop.
def sort(lst: list[int]) -> list[int]:
l1 = []
l2 = []
for i in lst:
if i % 2 == 0:
l1.append(i)
else:
l2.append(i)
x = l1 + l2
return x
def eto(lst: list[int]) -> list[int]:
if len(lst) == 1
return lst
else:
y = lst[0]
x = lst[1:]
return(eto(x))
I'm not sure how to proceed from here.
I suppose we are allowed to reorder the odd numbers, as long as they are in the second part of the list. I completed your proposition:
def eto(lst: list[int]) -> list[int]:
if len(lst) <= 1:
return lst
head = lst[0]
tail = lst[1:]
if head % 2 == 0:
return [head] + eto(tail)
return eto(tail) + [head]

Issue with iterating through Linked List in Python

I am trying to implement a Linked List from scratch in Python 3.7 and after many attempts, I can't seem to get the print_values() method to print all node values in the order that is expected (sequential). At this point I am not sure if the problem is with the append() method or the print_values() method.
class Node:
def __init__(self, node_value):
self.node_value = node_value
self.nextNode = None
class SinglyLinkedList:
# methods that should be available: get_size, insert_at, append, remove,
# update_node_value
def __init__(self):
self.head_node = None
self.tail_node = None
self.size = 0
def get_list_size(self):
"""This returns the value for the size variable which get incremented
every time a new node is added.
This implementation is better because it has a running time of O(1)
as opposed to iterating through the
whole list which has a running time of O(n)"""
return self.size
def append(self, value):
new_node = Node(value)
if self.head_node is None:
self.head_node = new_node
self.size += 1
else:
while self.head_node.nextNode is not None:
self.head_node = self.head_node.nextNode
self.head_node.nextNode = new_node
self.size += 1
def print_values(self):
current_node = self.head_node
list_values = []
while current_node.nextNode is not None:
list_values.append(current_node.node_value)
if current_node.nextNode.nextNode is None:
list_values.append(current_node.nextNode.node_value)
current_node = current_node.nextNode
if list_values is not None:
print("Linked list: " + str(list_values))
else:
print("Linked List is currently empty.")
# Helper code below.
new_ll = SinglyLinkedList()
new_ll.append("alpha")
print(new_ll.get_list_size())
new_ll.append("beta")
print(new_ll.get_list_size())
new_ll.append("gamma")
print(new_ll.get_list_size())
new_ll.append("delta")
print(new_ll.get_list_size())
new_ll.append("epsilon")
print(new_ll.get_list_size())
new_ll.append("zeta")
print(new_ll.get_list_size())
new_ll.print_values()
And all I am getting in the output is this:
1
2
3
4
5
6
Linked list: ['epsilon', 'zeta']
Typically a singly linked list only keeps track of the head. (Not the tail as well). So self.tail_node = None is not normally used.
When working with a linkedlist or a tree it will make your life a lot easier to work with recursion instead of using loops. Loops work fine if you just want to go over the list but if you want to change it then I would recommend a recursive solution.
That being said the issue isn't with your print it is with your append.
You can NEVER move the head node. You must always make a pointer so this caused the issue:
self.head_node = self.head_node.nextNode
Fix:
def append(self, value):
new_node = Node(value)
if self.head_node is None:
self.head_node = new_node
self.size += 1
else:
temp_head = self.head_node
while temp_head.nextNode is not None:
temp_head = temp_head.nextNode
temp_head.nextNode = new_node
self.size += 1
Recursive Solution:
def append(self, value):
new_node = Node(value)
self.size += 1
self.head_node = self.__recursive_append(self.head_node, new_node)
def __recursive_append(self, node, new_node):
if not node:
node = new_node
elif not node.nextNode:
node.nextNode = new_node
else:
node.nextNode = self.__recursive_append(node.nextNode, new_node)
return node
That being said I didn't realize this till after I redid your print so here is a cleaner print method using a python generator that may help you.
Generators is something you can use with python that you can't normally use with other programming languages and it makes something like turning a linked list into a list of values really easy to do:
def print_values(self, reverse=False):
values = [val for val in self.__list_generator()]
if values:
print("Linked list: " + str(values))
else:
print("Linked List is currently empty.")
def __list_generator(self):
'''
A Generator remembers its state.
When `yield` is hit it will return like a `return` statement would
however the next time the method is called it will
start at the yield statment instead of starting at the beginning
of the method.
'''
cur_node = self.head_node
while cur_node != None:
yield cur_node.node_value # return the current node value
# Next time this method is called
# Go to the next node
cur_node = cur_node.nextNode
Disclaimer:
The generator is good but I only did it that way to match how you did it (i.e. getting a list from the linkedlist). If a list is not important but you just want to output each element in the linkedlist then I would just do it this way:
def print_values(self, reverse=False):
cur_node = self.head_node
if cur_node:
print('Linked list: ', end='')
while cur_node:
print("'{}' ".format(cur_node.node_value), end='')
cur_node = cur_node.nextNode
else:
print("Linked List is currently empty.")
I agree with Error - Syntactical Remorse's answer in that the problem is in append and the body of the while loop...here is the example in pseudo code:
append 0:
head = alpha
append 1:
//skip the while loop
head = alpha
next = beta
append 2:
//one time through the while loop because next = beta
head = beta
//beta beta just got assigned to head, and beta doesn't have next yet...
next = gamma
append 3:
//head is beta, next is gamma...the linked list can only store 2 nodes
head = gamma //head gets next from itself
//then has no next
next = delta
...etc.

Print permutation tree python3

I have list of numbers and I need to create tree and print all permutations, e.g. for [1, 2, 3] it should print
123
132
213
231
312
321
Currently my code prints first element only once:
123
32
213
31
312
21
How do I fix it?
class Node(object):
def __init__(self):
self.children = None
self.data = None
self.depth = None
class PermutationTree(object):
def __init__(self, numbers):
numbers = list(set(numbers))
numbers.sort()
self.depth = len(numbers)
self.tree = self.generate_root(numbers)
self.create_tree(numbers, self.tree.children, 1)
def generate_root(self, numbers):
root = Node()
root.children = []
root.depth = 0
for i in numbers:
tree = Node()
tree.data = i
tree.depth = 1
root.children.append(tree)
return root
def create_tree(self, numbers, children, depth):
depth += 1
if depth == self.depth + 1:
return
for child in children:
reduced_numbers = list(numbers)
reduced_numbers.remove(child.data)
child.children = []
for j in reduced_numbers:
tree = Node()
tree.data = j
tree.depth = depth
child.children.append(tree)
self.create_tree(reduced_numbers, child.children, depth)
def print_tree(self):
self.print(self.tree)
def print(self, node):
for i in node.children:
print(i.data, end="")
if not i.depth == self.depth:
self.print(i)
print("")
test = [1, 2, 3]
permutation_tree = PermutationTree(test)
permutation_tree.print_tree()
This is pretty good example showing why side effects should be done in as few places as possible.
Side effects everywhere
Your approach is like this: "I will print one digit every time it is needed." This approach gives you limited control over side effects. Your code prints many times in different recursion depths and it's hard to keep track of what is going on.
Side effects in as few places as possible
Another approach to this problem is creating side effects in one place. "I will generate all permutations first and then print them." Such a code is easier to debug and can give you better control of what is going on in your code.
If you write your code like this:
def print_tree(self): # this function takes care about printing
for path in self.generate_path(self.tree):
print(path)
def generate_perm(self, node): #this function takes care about generating permutations
if not node.children:
return [str(node.data)]
app_str = str(node.data) if node.data else ""
res= []
for child in node.children:
for semi_path in self.generate_perm(child):
res.append (app_str + semi_path)
return res
P.S.
generate_perm can be rewritten to be more efficient with yields.

how to multiply all numbers in a stack

Trying to multiply all the numbers in a stack, I originally thought of popping all elements into a list and then multiplying but wasn't sure how to/ if that was right.
this is my current code but I'm getting:
TypeError: 'method' object cannot be interpreted as an integer.
def multi_stack(s):
stack = Stack()
mult = 1
size = my_stack.size
for number in range(size):
tmp = my_stack.pop(size)
mult = mult * tmp
L.append(tmp)
for number in range(size):
my_stack.push(L.pop())
print(must)
I made a test case aswell
my_stack = Stack()
my_stack.push(12)
my_stack.push(2)
my_stack.push(4)
my_stack.push(40)
print(multi_stack(my_stack))
print(my_stack.size())`
this should print out :
3840
0
The Stack class I'm using
class Stack():
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def push(self,items):
return self.items.append(items)
def pop(self):
if self.is_empty() == True:
raise IndexError("The stack is empty!")
else:
return self.items.pop()
def peek(self):
if self.is_empty() == True:
raise IndexError("The stack is empty!")
else:
return self.items[len(self.items) - 1]
def size(self):
return len(self.items)
Python lists support append() and pop() methods that allow you to replicate LIFO stack behavior. Use append() to add to the end and pop() to remove the last element.
However, the underlying data structure is still a list. You can use many things to multiply a list together. for example, assuming a non-empty list:
import functools
mylist = [i for i in range(1, 10)]
product = functools.reduce(lambda x, y: x * y, mylist)
or
mylist = [i for i in range(1, 10)]
product = mylist[0]
for j in mylist[1:]:
product *= j
EDIT: Here is an example using your Stack class:
import functools
stack = Stack()
stack.push(1)
stack.push(3)
stack.push(9)
def multi_stack(s):
"""
s: a Stack() object
"""
return functools.reduce(lambda x, y: x * y, s.items)
def multi_stack_readable(s):
"""
s: a Stack() object
"""
if s.size() > 1:
product = s.items[0]
for i in s.items[1:]:
product *= i
return product
elif s.size() == 1:
return s.items
else:
raise IndexError("the stack is empty!")
print(multi_stack(stack))
print(multi_stack_readable(stack))
Using lambda functions is sometimes considered less readable, so I included a more readable version using a for loop. Both produce the same result.
Your code doesnt work because size = my_stack.size returns a method object and not the integer you expected; you forgot to add the parentheses at the end to actually call the method. So when you tried for number in range(size):, you get an exception because you are passing a method object instead of an integer to range(). There are also a bunch of other mistakes: you didnt use the parameter passed to the function at all, instead affecting global variable my_stack (unless that was your intent); you're performing operations on some unknown variable L; you created stack at the top of your function and did nothing with it, and so on. In general, too convoluted for such a simple goal. There are more efficient ways to do this but correcting your code:
def multi_stack(s):
mult = 1
size = s.size()
for i in range(size):
tmp = s.pop()
mult = mult * tmp
return mult
This should return your expected product, though it wont empty the stack. If you want to do that, then get rid of the function parameter, and substitute s for my_stack as before.

Tree traversals python

I have to define three functions: preorder(t):, postorder(t):, and inorder(t):.
Each function will take a binary tree as input and return a list. The list should then be ordered in same way the tree elements would be visited in the respective traversal (post-order, pre-order, or in-order)
I have written a code for each of them, but I keep getting an error when I call another function (flat_list()), I get an index error thrown by
if not x or len(x) < 1 or n > len(x) or x[n] == None:
IndexError: list index out of range
The code for my traversal methods is as follows:
def postorder(t):
pass
if t != None:
postorder(t.get_left())
postorder(t.get_right())
print(t.get_data())
def pre_order(t):
if t != None:
print(t.get_data())
pre_order(t.get_left())
pre_order(t.get_right())
def in_order(t):
pass
if t != None:
in_order(t.get_left())
print(t.get_data())
in_order(t.get_right())
def flat_list2(x,n):
if not x or len(x) < 1 or n > len(x) or x[n] == None:
return None
bt = BinaryTree( x[n] )
bt.set_left( flat_list2(x, 2*n))
bt.set_right(flat_list2(x, 2*n + 1))
return bt
this is how i call flat_list2
flat_node_list = [None, 55, 24, 72, 8, 51, None, 78, None, None, 25]
bst = create_tree_from_flat_list2(flat_node_list,1)
pre_order_nodes = pre_order(bst)
in_order_nodes = in_order(bst)
post_order_nodes = post_order(bst)
print( pre_order_nodes)
print( in_order_nodes)
print( post_order_nodes)
You should actually write three function that return iterators. Let the caller decide whether a list is needed. This is most easily done with generator functions. In 3.4+, 'yield from` can by used instead of a for loop.
def in_order(t):
if t != None:
yield from in_order(t.get_left())
yield t.get_data()
yield from in_order(t.get_right())
Move the yield statement for the other two versions.
First things first, I noticed that your indentation was inconsistent in the code block that you provided (fixed in revision). It is critical that you ensure that your indentation is consistent in Python or stuff will go south really quickly. Also, in the code below, I am assuming that you wanted your t.get_data() to still fall under if t != None in your postorder(t), so I have indented as such below. And lastly, I noticed that your method names did not match the spec you listed in the question, so I have updated the method names below to be compliant with your spec (no _ in the naming).
For getting the list, all you need to do is have your traversal methods return a list, and then extend your list at each level of the traversal with the other traversed values. This is done in lieu of printing the data.
def postorder(t):
lst = []
if t != None:
lst.extend(postorder(t.get_left()))
lst.extend(postorder(t.get_right()))
lst.append(t.get_data())
return lst
def preorder(t):
lst = []
if t != None:
lst.append(t.get_data())
lst.extend(preorder(t.get_left()))
lst.extend(preorder(t.get_right()))
return lst
def inorder(t):
lst = []
if t != None:
lst.extend(inorder(t.get_left()))
lst.append(t.get_data())
lst.extend(inorder(t.get_right()))
return lst
This will traverse to the full depths both left and right on each node and, depending on if it's preorder, postorder, or inorder, will append all the traversed elements in the order that they were traversed. Once this has occurred, it will return the properly ordered list to the next level up to get appended to its list. This will recurse until you get back to the root level.
Now, the IndexError coming from your flat_list, is probably being caused by trying to access x[n] when n could be equal to len(x). Remember that lists/arrays in Python are indexed from 0, meaning that the last element of the list would be x[n-1], not x[n].
So, to fix that, replace x[n] with x[n-1]. Like so:
def flat_list2(x,n):
if not x or len(x) < 1 or n < 1 or n > len(x) or x[n-1] == None:
return None
bt = BinaryTree( x[n-1] )
bt.set_left( flat_list2(x, 2*n))
bt.set_right(flat_list2(x, 2*n + 1))
return bt

Resources