Tree traversals python - python-3.x

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

Related

List comprehention works but normal for loop doesn't - Python

I defined a function to change an element of a list to be number 0, if this element is not a number. It works using list comprehension but it doesn't work when I use a normal for loop.
I'm trying to understand what's is the error in the for loop.
See the code below:
def zerozero(mylist):
mylist = [0 if type(x) == str else x for x in mylist]
return mylist
def zerozero2(mylist):
for x in mylist:
if type(x) == str:
x = 0
else:
x = x
return mylist
Your second function is not quite equivalent. You would need something like this:
def zerozero2(mylist):
new_list = []
for x in mylist:
if type(x) == str:
new_list.append(0)
else:
new_list.append(x)
return new_list
In this manner you can mimic the functionality of the list comprehension, creating a new list and appending items to it as you iterate through.
If you want to modify your list 'in place', you can use this sort of construction:
for idx, x in enumearte(mylist):
if type(x) == str:
mylist[idx] = 0
else:
mylist[idx] = x
However, practically speaking this is unlikely to have much impact on your code efficiency. You can't do this with a list comprehension, and in either case you can just re-assign the new list back to your original variable when you return from the function:
mylist = zerozeroX(mylist)
So what happens is your function is returning the same list as your input.
What you should do is create an empty list first. For example my_list_0 = [].
def zerozero2(mylist):
my_list_0 = []
for x in mylist:
if type(x) == str:
x=0
else:
x=x
my_list_0.append(x)
return my_list_0
The list comprehension essentially returns the new values into your original list, so this is why it is different.

Python: Unexpected result in a recursive function

I've written a recursive function to gauge, e.g. list depth and for some reason it returns unexpected results.
I have two functions:
1. checks if the object is iterable
2. gauges the depth of the object, e.g. list
I think I'm missing something in the second function but I couldn't wrap my head around why exactly variable n when returned from else turns into a funny result.
I set print to see how n gets changed in every stage and it seemed working as expected but when returned from else it turns into a wrong number.
Here are two functions' code:
def isiterable(obj):
'''
takes in an obj and returns 1 if iterable or 0 if not
strings are discarded as iterable
:param obj: any object
:return: int
'''
if isinstance(obj, str):
return 0
else:
try:
iter(obj)
return 1
except TypeError as err:
return 0
my second function is recursive where I'm experiencing problems
def get_depth(a, n=0):
if isiterable(a):
return n + f(a[0], n+1)
else:
return n
I've three examples:
a = [[[1,2], [3,4]], [[5,6],[7,8]]]
b = [[1,2], [2,3]]
c = [2]
I'm expecting get_depth to return 3 for list a, 2 for list b and 1 for list c.
for some reason results for a get doubled and return 6. In b case it is 3 instead of 2.
Many thanks
You don't need to add n when you return from get_depth.
def get_depth(a, n=0):
if isiterable(a):
return get_depth(a[0], n+1)
else:
return n
Because, when a have more depth, you will calculate the get_depth function again witn n+1 which is already count the depth correctly and the extras are not needed.
Btw, you have to think about what if this case?
d = [1, 2, [3, 4]]
I can modify a bit such as:
def get_depth(a, n=0):
if isiterable(a):
temp = []
for i in range(0, len(a)):
temp.append(get_depth(a[i], n+1))
return max(temp)
else:
return n

why is siftdown working in heapsort, but not siftup?

I have a programming assignment as follows:
You will need to convert the array into a heap using only O(n) swaps, as was described in the lectures. Note that you will need to use a min-heap instead of a max-heap in this problem. The first line of the output should contain single integer m — the total number of swaps. m must satisfy conditions 0 ≤ m ≤ 4n. The next m lines should contain the swap operations used to convert the array a into a heap. Each swap is described by a pair of integers i,j — the 0-based indices of the elements to be swapped
I have implemented a solution using sifting up technique by comparing with parent's value which gave solutions to small text cases, when number of integers in array is less than 10,verified by manual checking, but it could not pass the test case with 100000 integers as input.
this is the code for that
class HeapBuilder:
def __init__(self):
self._swaps = [] #array of tuples or arrays
self._data = []
def ReadData(self):
n = int(input())
self._data = [int(s) for s in input().split()]
assert n == len(self._data)
def WriteResponse(self):
print(len(self._swaps))
for swap in self._swaps:
print(swap[0], swap[1])
def swapup(self,i):
if i !=0:
if self._data[int((i-1)/2)]> self._data[i]:
self._swaps.append(((int((i-1)/2)),i))
self._data[int((i-1)/2)], self._data[i] = self._data[i],self._data[int((i-1)/2)]
self.swapup(int((i-1)/2))
def GenerateSwaps(self):
for i in range(len(self._data)-1,0,-1):
self.swapup(i)
def Solve(self):
self.ReadData()
self.GenerateSwaps()
self.WriteResponse()
if __name__ == '__main__':
heap_builder = HeapBuilder()
heap_builder.Solve()
on the other hand i have implemented a heap sort using sifting down technique with similar comparing process, and this thing has passed every test case.
following is the code for this method
class HeapBuilder:
def __init__(self):
self._swaps = [] #array of tuples or arrays
self._data = []
def ReadData(self):
n = int(input())
self._data = [int(s) for s in input().split()]
assert n == len(self._data)
def WriteResponse(self):
print(len(self._swaps))
for swap in self._swaps:
print(swap[0], swap[1])
def swapdown(self,i):
n = len(self._data)
min_index = i
l = 2*i+1 if (2*i+1<n) else -1
r = 2*i+2 if (2*i+2<n) else -1
if l != -1 and self._data[l] < self._data[min_index]:
min_index = l
if r != - 1 and self._data[r] < self._data[min_index]:
min_index = r
if i != min_index:
self._swaps.append((i, min_index))
self._data[i], self._data[min_index] = \
self._data[min_index], self._data[i]
self.swapdown(min_index)
def GenerateSwaps(self):
for i in range(len(self._data)//2 ,-1,-1):
self.swapdown(i)
def Solve(self):
self.ReadData()
self.GenerateSwaps()
self.WriteResponse()
if __name__ == '__main__':
heap_builder = HeapBuilder()
heap_builder.Solve()
can someone explain what is wrong with sift/swap up method?
Trying to build a heap by "swapping up" from the bottom won't always work. The resulting array will not necessarily be a valid heap. For example, consider this array: [3,6,2,4,5,7,1]. Viewed as tree that is:
3
4 2
6 5 7 1
Your algorithm starts at the last item and swaps up towards the root. So you swap 1 with 2, and then you swap 1 with 3. That gives you:
1
4 3
6 5 7 2
You then continue with the rest of the items, none of which have to be moved.
The result is an invalid heap: that last item, 2, should be the parent of 3.
The key here is that the swapping up method assumes that when you've processed a[i], then the item that ends up in that position is in its final place. Contrast that to the swap down method that allows repeated adjustment of items that are lower in the heap.

Inserting int into sorted multidimensional list

I need to insert an int into a sorted list.
Here's what I got so far:
def insert_in_list(x, tree, index=0):
if not tree:
return tree
elif x < tree[index]:
tree.insert(index, x)
return tree
else:
return insert_in_list(x, tree, index+1)
This is working on all one-dimensional lists for example, [1,5,10]. However, I need it to work on any list containing additional lists, for example: [1,[5,7[8,9,10]]18,22].
Tried this:
def insert_in_list(x, tree, index=0):
if not tree:
return x
elif isinstance(tree[0], list):
return [insert_in_list(x, tree[0])] + insert_in_list(x, tree[1:])
elif x < tree[index]:
tree.insert(index, x)
return tree
else:
return insert_in_list(x, tree, index+1)
However, insert_in_list(4, [[2,3,6], 8, 10])) returns [[[[2, 3, 4, 6]]], 4, 8, 10]. So the recursion doesn't stop after inserting the first 4. help me out
It happen because you need to tell your function to stop looking for x < item when the upper bound is found.
This is an additional signal (variable done) that propagates as soon as an upper bound is found.
def insert_in_list(x, tree, done = False):
for index, item in enumerate(tree):
if isinstance(item, list):
done = insert_in_list(x, item)
elif done:
return True
elif x < item:
tree.insert(index, x)
return True
Additionnaly
you don't need the index as parameter since you'll insert inside the loop, other instances of your function don't need to know that last index.
list are mutable, so it is allways the same list even if you don't pass it as parameter.

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.

Resources