How to find the sum of nodes in a tree without recursion - python-3.x

I've tried to solve this problem a few different ways.
The Node for my code is defined as:
A Node is an object
- value : Number
- children : List of Nodes
class Node:
def __init__(self, key, childnodes):
self.key = key
self.childnodes = childnodes
def __repr__(self):
return f'Node({self.key!r}, {self.childnodes!r})'
testTree = Node(1, [Node(2, []), Node(3, [Node(4, [Node(5, []), Node(6, [Node(7, [])])])])])
I've gotten close to completing the problem with the code:
def sum_of_nodes(root):
sum = 0
while root:
sum += root.key
print(sum)
root = root.childnodes
print(root)
root = root.pop()
print(root)
print(sum)
However, it skips some portions of the code and I'm not sure how to go about fixing it. The result of the above code is:
1
[Node(2, []), Node(3, [Node(4, [Node(5, []), Node(6, [Node(7, [])])])])]
Node(3, [Node(4, [Node(5, []), Node(6, [Node(7, [])])])])
4
[Node(4, [Node(5, []), Node(6, [Node(7, [])])])]
Node(4, [Node(5, []), Node(6, [Node(7, [])])])
8
[Node(5, []), Node(6, [Node(7, [])])]
Node(6, [Node(7, [])])
14
[Node(7, [])]
Node(7, [])
21
[]
Along with an error:
Traceback (most recent call last):
File "D:/Documents/project1.py", line 191, in <module>
print(f'itersum_of_nodes(testTree) => {itersum_of_nodes(testTree)}') # 28
File "D:/Documents/project1.py", line 108, in sum_of_nodes
root = root.pop()
IndexError: pop from empty list
I've also tried the method taught at geeksforgeeks.org/inorder-tree-traversal-without-recursion/ however, my children nodes are defined as a list rather than .right or .left and I'm not sure how to get the info I need out of it because of that. The code is:
stack = []
sum = 0
current = root
while True:
if current.childnodes[0] is not None:
stack.append(current)
current = current.childnodes[0]
elif stack:
current = stack.pop()
sum += current.value
current = current.childnodes[1]
else:
break

Here is a working version of while loop that traverses your tree without recursion. The main problem with this is that your nodes don't store the children individually, but rather as a list. This means that you need to be vigilant in checking how many children the node actually has.
def sum_of_nodes(root):
sum = 0
stack = []
# While the current node is not none
while root:
# Add the value of the current node to the sum
sum += root.key
# Check if the current node has children
if root.childnodes:
# If it has two children, append the second child to the stack
if len(root.childnodes) == 2:
stack.append(root.childnodes[1])
# Set the first child as the current node
root = root.childnodes[0]
# If the current node doesn't have any children, take one from the stack
elif stack:
root = stack.pop()
# If we run out of children in the stack, end the while loop
else:
break
return sum

Related

get dimension or nesting level of element in list

the_list = ['mob', 'rob', ['hix', ['lu', 'mu'], 'rix', 'mob']]
How do I get the nesting level of an element in the list?
I'm trying to do something like this:
for the_element in the_list:
the_dimension = get_dimension_of_element(the_element)
print(the_element, the_dimension)
which would output:
'mob', 1
'rob', 1
'hix', 2
'lu', 3
'mu', 3
'rix', 2
'mob', 2
You probably want to use recursion, because what you're doing depends on whether the element is a list or just a string.
def level_print(data, level=1):
for elem in data:
if isinstance(elem, (list, tuple)):
level_print(elem, level + 1)
else:
print("{!r}, {}".format(elem, level))
level_print(the_list)
Here's a function that returns the resulting output.
def level_enumerate(data, level=1):
result = []
for elem in data:
if isinstance(elem, (list, tuple)):
result.extend(level_enumerate(elem, level + 1))
else:
result.append((elem, level))
return result
print(level_enumerate(the_list))

getting "AttributeError: 'list' object has no attribute 'val' " while compareing binary tree in python

I am trying to compare two binary tree p and q but continuosly an error is raised saying "AttributeError: 'list' object has no attribute 'val'". I am not able to get over the error.
Can you please help me in resolving the issue and improve my understanding please?
Please find the code below:
# Definition for a binary tree node.
p = [1,2,3]
q = [1,2,3]
class TreeNode(object):
def __init__(self, v=0, l=None, r=None):
self.val = v
self.left = l
self.right = r
def compareBinaryTree(self, p, q):
stack = [(p, q)]
while stack:
node1, node2 = stack.pop()
print(node1,node2)
if not node1 and not node2:
continue
elif None in [node1, node2]:
return False
else:
if node1.val != node2.val:
return False
stack.append((node1.right, node2.right))
stack.append((node1.left, node2.left))
return True
Please find below the error which is displayed while running the code:
>>>TreeNode.compareBinaryTree(t,p,q)
[1, 2, 3] [1, 2, 3]
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-26-1372e5b88a65> in <module>
----> 1 TreeNode.compareBinaryTree(t,p,q)
<ipython-input-23-5eef65d6c59b> in compareBinaryTree(self, p, q)
18 return False
19 else:
---> 20 if node1.val != node2.val:
21 return False
22 stack.append((node1.right, node2.right))
**AttributeError: 'list' object has no attribute 'val'**
p and q seem to be lists and not trees. [1, 2, 3] is a list and I think you mean to pass another instance of TreeNode instead.

How to find best solution from all brute force combinations?

I want to find the best solution from all brute force combinations of a dictionary. For the context of the problem, I need to find out the minimum number of trips needed to transport all the cows given a weight limit.
The combinations are already given to me with the helper functions get_partitions. The function returns a nested list, with each inner list represents a trip and names of cows on that trip.
Helper functions:
def partitions(set_):
if not set_:
yield []
return
for i in range(2**len(set_)//2):
parts = [set(), set()]
for item in set_:
parts[i&1].add(item)
i >>= 1
for b in partitions(parts[1]):
yield [parts[0]]+b
def get_partitions(set_):
for partition in partitions(set_):
yield [list(elt) for elt in partition]
What I tried to do was sorting all combinations by length then iterate over them with a nested loop. If the total weight exceed the limit, then I break out of the inner loop and append the next sublist to another list. The problem is that the next sublist still contain leftover lists from the partition since their total weight is below the limit.
My code:
def brute_force_cow_transport(cows, limit):
# Generate and sort all combinations by length
partitions = [item for item in get_partitions(cows)]
partitions.sort(key=len)
# Iterate over each sublist of combinations
possibles = []
for partition in partitions:
trips = []
for section in partition:
total = sum([cows.get(cow) for cow in section])
if total > limit:
break
else:
# Appending next sublists create duplicates
trips.append(section)
possibles.append(trips)
# Remove duplicates from possible solutions
best = []
for item in possibles:
if item and item not in best:
best.append(item)
return min(best)
When I run my function, it keeps returning a different result each time. I think it's because the leftover sublists I appended to the results is causing the problem but I'm not sure:
cows = {'MooMoo': 50, 'Miss Bella': 25, 'Boo': 20,
'Milkshake': 40, 'Horns': 25, 'Lotus': 40}
>>> brute_force_cow_transport(cows, limit=100)
[['Boo', 'Miss Bella', 'MooMoo']]
Correct result:
[['MooMoo', 'Horns', 'Miss Bella'], ['Milkshake', 'Lotus', 'Boo']]
If anyone can help me point out where I went wrong, that would be greatly appreciated.
Edit: Added helper functions
We can approach this as a depth-first-search problem.
def getCows(dict, limit):
best_solution = []
best_solution_score = 0
def dfs(current_cows, current_total):
nonlocal best_solution_score
nonlocal best_solution
if current_total > best_solution_score:
#replace best solution
best_solution = [tuple(sorted(current_cows))]
best_solution_score = current_total
elif current_total == best_solution_score:
#add to best solution
best_solution.append(tuple(sorted(current_cows)))
for cow, weight in dict.items():
if cow not in current_cows and current_total + weight <= limit:
#if adding next cow under limit recurse
dfs(current_cows + [cow], current_total + weight)
dfs([], 0)
return list(set(best_solution)) #remove duplicates
cows = {'MooMoo': 50, 'Miss Bella': 25, 'Boo': 20,
'Milkshake': 40, 'Horns': 25, 'Lotus': 40}
print(getCows(cows, limit=100))
>>>[('Horns', 'Miss Bella', 'MooMoo'), ('Boo', 'Lotus', 'Milkshake')]

Python 3.x - function args type-testing

I started learning Python 3.x some time ago and I wrote a very simple code which adds numbers or concatenates lists, tuples and dicts:
X = 'sth'
def adder(*vargs):
if (len(vargs) == 0):
print('No args given. Stopping...')
else:
L = list(enumerate(vargs))
for i in range(len(L) - 1):
if (type(L[i][1]) != type(L[i + 1][1])):
global X
X = 'bad'
break
if (X == 'bad'):
print('Args have different types. Stopping...')
else:
if type(L[0][1]) == int: #num
temp = 0
for i in range(len(L)):
temp += L[i][1]
print('Sum is equal to:', temp)
elif type(L[0][1]) == list: #list
A = []
for i in range(len(L)):
A += L[i][1]
print('List made is:', A)
elif type(L[0][1]) == tuple: #tuple
A = []
for i in range(len(L)):
A += list(L[i][1])
print('Tuple made is:', tuple(A))
elif type(L[0][1]) == dict: #dict
A = L[0][1]
for i in range(len(L)):
A.update(L[i][1])
print('Dict made is:', A)
adder(0, 1, 2, 3, 4, 5, 6, 7)
adder([1,2,3,4], [2,3], [5,3,2,1])
adder((1,2,3), (2,3,4), (2,))
adder(dict(a = 2, b = 433), dict(c = 22, d = 2737))
My main issue with this is the way I am getting out of the function when args have different types with the 'X' global. I thought a while about it, but I can't see easier way of doing this (I can't simply put the else under for, because the results will be printed a few times; probably I'm messing something up with the continue and break usage).
I'm sure I'm missing an easy way to do this, but I can't get it.
Thank you for any replies. If you have any advice about any other code piece here, I would be very grateful for additional help. I probably have a lot of bad non-Pythonian habits coming from earlier C++ coding.
Here are some changes I made that I think clean it up a bit and get rid of the need for the global variable.
def adder(*vargs):
if len(vargs) == 0:
return None # could raise ValueError
mytype = type(vargs[0])
if not all(type(x) == mytype for x in vargs):
raise ValueError('Args have different types.')
if mytype is int:
print('Sum is equal to:', sum(vargs))
elif mytype is list or mytype is tuple:
out = []
for item in vargs:
out += item
if mytype is list:
print('List made is:', out)
else:
print('Tuple made is:', tuple(out))
elif mytype is dict:
out = {}
for i in vargs:
out.update(i)
print('Dict made is:', out)
adder(0, 1, 2, 3, 4, 5, 6, 7)
adder([1,2,3,4], [2,3], [5,3,2,1])
adder((1,2,3), (2,3,4), (2,))
adder(dict(a = 2, b = 433), dict(c = 22, d = 2737))
I also made some other improvements that I think are a bit more 'pythonic'. For instance
for item in list:
print(item)
instead of
for i in range(len(list)):
print(list[i])
In a function like this if there are illegal arguments you would commonly short-cuircuit and just throw a ValueError.
if bad_condition:
raise ValueError('Args have different types.')
Just for contrast, here is another version that feels more pythonic to me (reasonable people might disagree with me, which is OK by me).
The principal differences are that a) type clashes are left to the operator combining the arguments, b) no assumptions are made about the types of the arguments, and c) the result is returned instead of printed. This allows combining different types in the cases where that makes sense (e.g, combine({}, zip('abcde', range(5)))).
The only assumption is that the operator used to combine the arguments is either add or a member function of the first argument's type named update.
I prefer this solution because it does minimal type checking, and uses duck-typing to allow valid but unexpected use cases.
from functools import reduce
from operator import add
def combine(*args):
if not args:
return None
out = type(args[0])()
return reduce((getattr(out, 'update', None) and (lambda d, u: [d.update(u), d][1]))
or add, args, out)
print(combine(0, 1, 2, 3, 4, 5, 6, 7))
print(combine([1,2,3,4], [2,3], [5,3,2,1]))
print(combine((1,2,3), (2,3,4), (2,)))
print(combine(dict(a = 2, b = 433), dict(c = 22, d = 2737)))
print(combine({}, zip('abcde', range(5))))

Remove certain item from list

I'm working out how to remove a specific item from a list.
"peppers", "cheese", "mushrooms", "bananas", "peppers"
I can locate the item "peppers", and change it to "gone!", but I really want to deleting the item, using
del blist[idx]
But that causes an error and I don't know why.
myList = ["peppers", "cheese", "mushrooms", "bananas", "peppers"]
def findInList (needle, haystack):
needle = needle.lower()
findy = []
# loops over list elements
for i in range(0, len(haystack)):
temp = haystack[i].lower()
idx = temp.find(needle)
if (idx != -1): findy.append(i)
return findy
def deleteItemInList(alist, blist):
for i in range(0, len(alist)):
idx = alist[i]
blist[idx] = "Gone!"
# del blist[idx]
# find items in list
mySearch = findInList("Peppers", myList)
# remove item from list
deleteItemInList(mySearch, myList)
print myList
Traceback: as follows
Traceback (most recent call last):
File "delete_in_list.py", line 23, in <module>
deleteItemInList(mySearch, myList)
File "delete_in_list.py", line 16, in deleteItemInList
blist[idx] = "Gone!"
IndexError: list assignment index out of range
Could someone look over the code above and point out where I'm going wrong.
You can use a list comprehension for this.
def removeWord(needle, haystack):
return [word for word in haystack if word.lower() != needle.lower()]
To find an element use this function. Or alternatively just define it as usual:
>>> find = lambda _list, query: [item.lower() for item in _list].index(query.lower())
>>> l = ['red', 'pepper']
>>> q = 'Pepper'
>>> find(l, q)
1
To remove by index just use del:
>>> del l[find(l, q)]
>>> l
['red']
I finally figured it out! Whilst iterating over the list deleting items in the list I was effectively sawing off the branch I was sitting on.
You need to loop over the list in reverse:
def deleteItemInList(alist, blist):
for i in range(len(alist) -1, -1, -1):
idx = alist[i]
del blist[idx]

Resources