Yield all full root-leaf paths through tree structure in Python - python

I'm trying to adapt this answer in two ways:
I want to make the traverse function a class method, and
I want a call to traverse to yield the list of all root-to-leaf paths (list of lists) in the tree
First change was trivial, second one I'm struggling with. Here's my class definition:
class createnode:
""" thanks to https://stackoverflow.com/a/51911296/1870832"""
def __init__(self,nodeid):
self.nodeid=nodeid
self.child=[]
def __str__(self):
print(f"{self.nodeid}")
def traverse(self, path = []):
path.append(self.nodeid)
if len(self.child) == 0:
#print(path)
yield path
path.pop()
else:
for child in self.child:
child.traverse(path)
path.pop()
I construct a tree with:
ROOT_NODE = 0
root = createnode(ROOT_NODE)
lvl1 = [createnode(1), createnode(2), createnode(3)]
root.child += lvl1
root.child[0].child += [createnode(4), createnode(5)]
root.child[1].child += [createnode(6), createnode(7)]
root.child[2].child += [createnode(8), createnode(9)]
Desired output for printing all full root-leaf paths (e.g. w/ code below)
paths = root.traverse()
for p in paths:
print(p)
is:
[0, 1, 4]
[0, 1, 5]
[0, 2, 6]
[0, 2, 7]
[0, 3, 8]
[0, 3, 9]

You need to look into a recursive generator.
I have corrected your setup code:
class createnode:
""" thanks to https://stackoverflow.com/a/51911296/1870832"""
def __init__(self,nodeid):
self.nodeid=nodeid
self.child=[]
def __str__(self):
print(f"{self.nodeid}")
def traverse(self, path = None):
if path is None:
path = []
path.append(self.nodeid)
if len(self.child) == 0:
yield path
path.pop()
else:
for child in self.child:
yield from child.traverse(path)
path.pop()
ROOT_NODE = 0
root = createnode(ROOT_NODE)
children = [createnode(32), createnode(5)]
root.child += children
paths = root.traverse()
for p in paths:
print(p)
Output:
[0, 32]
[0, 5]

I don't have any experience with yield yet but I'd do it like this:
class createnode:
""" thanks to https://stackoverflow.com/a/51911296/1870832"""
def __init__(self,nodeid):
self.nodeid=nodeid
self.child=[]
def __str__(self):
print(f"{self.nodeid}")
def traverse(self, path = []):
path.append(self.nodeid)
if len(self.child) == 0:
print(path)
path.pop()
else:
for child in self.child:
child.traverse(path)
path.pop()
ROOT_NODE = 0
root = createnode(ROOT_NODE)
lvl1 = [createnode(1), createnode(2), createnode(3)]
root.child += lvl1
root.child[0].child += [createnode(4), createnode(5)]
root.child[1].child += [createnode(6), createnode(7)]
root.child[2].child += [createnode(8), createnode(9)]
root.traverse()
[0, 1, 4]
[0, 1, 5]
[0, 2, 6]
[0, 2, 7]
[0, 3, 8]
[0, 3, 9]

Related

Python: Why are my children not creating children?

Here is the code: (from MAIN file)
def BestFirstSearch(startingBoard):
Q = [startingBoard]
Visited = []
while (len(Q) != 0):
Q.sort()
currentQ = Q.pop(0)
Visited.append(currentQ)
# print(currentQ)
if (currentQ.Board == currentQ.GOAL):
return True
currentQ.createChildrenBoards()
for items in currentQ.Children:
if items not in Visited:
Q.append(items)
print(len(Q))
print(currentQ)
return False
(from CLASS file):
def createChildrenBoards(self):
""" Creates the set of potential children Boards from the current Board """
row = self.X
col = self.Y
assert( (row >=0 and row < BoardClass.N)
and
(col >=0 and col < BoardClass.N) )
newChildrenBoards = []
#print(self.Board[row][col])
# UP(NORTH): slide empty (0) space up
if ( row != 0 ):
newChildBoard = self.copyCTOR()
newChildBoard.Parent = self
newChildBoard.X = row-1
newChildBoard.Y = col
holdCell = newChildBoard.Board[newChildBoard.X][newChildBoard.Y]
newChildBoard.Board[newChildBoard.X][newChildBoard.Y] = 0
newChildBoard.Board[row][col] = holdCell
newChildrenBoards.append(newChildBoard)
for puzzle in newChildrenBoards:
puzzle.computeDistanceFromGoal()
self.Children = newChildrenBoards
Here are portions of the code I'm working with. I initialized the starting board in a class that constructs the puzzle. Then in my main I would call the create children function which creates a list of children based on where you can move the zero (north being an example of how I would move 0).
The puzzle looks like this:
Goal = [ [0, 1, 2], [3, 4, 5], [6, 7, 8] ]
Puzzle = [ [3, 1, 2], [4, 7, 5], [6, 8, 0] ]
I'm not getting why the queue won't add more children from the children created from the starting board. I'm hoping that I can get feedback that will help me understand why my loop isn't registering the "grandchildren". Thank you!

Why __str__(self) doesn't work when calling the print() function?

I'm diving into OOP and learning magic (or dunder) techniques. Python 3.8.8.
I created class FreqStack() with a pop() method that removes the most frequent elements and returns an updated stack.
class FreqStack():
def __init__(self, lst:list = None):
if lst is None:
self.stack = []
else:
self.stack = lst[::-1]
def push(self, el: int):
self.stack.insert(0, el)
return self.stack
def pop(self):
if len(self.stack) != 0:
hash_map = {}
for el in self.stack:
hash_map[el] = hash_map.get(el, 0) + 1
most_freq_el = max(hash_map, key=hash_map.get)
while most_freq_el in self.stack:
self.stack.remove(most_freq_el)
return self.stack
else:
return 'Stack is empty!'
def __str__(self):
return '\n|\n'.join(str(el) for el in self.stack)
I also added the dunder method str(), which, as far as I understand correctly, must return a custom string when calling the print() function.
However, the print() function in the example below, instead of returning a string, returns a list.
lst = [1, 1, 1, 5, 5, 5, 3, 3, 3, 7, 7, 9]
freq_stack = FreqStack(lst)
for i in range(6):
print(freq_stack.pop())
Output:
[9, 7, 7, 5, 5, 5, 1, 1, 1]
[9, 7, 7, 1, 1, 1]
[9, 7, 7]
[9]
[]
Stack is empty!
I googled everything related to this problem, and couldn't solve it. What am I doing wrong?
You are printing the return value of pop, not the freq_stack itself.
The __str__ method is for the freq_stack object, so you may try something like:
freq_stack.pop()
print(freq_stack)

Binary Tree BFS without queue

I am working on a solution for https://leetcode.com/problems/binary-tree-level-order-traversal/submissions/.
I want to implement level order traversal without using a queue since that solution is quite trivial.
I have written the following code, which, in theory should work, but is giving the wrong result. I cant work out why.
Any help would be appreciated
class Node:
# A utility function to create a new node
def __init__(self, key):
self.val = key
self.left = None
self.right = None
def height(root):
if root is None:
return 0
else:
heightL = height(root.left)
heightR = height(root.right)
maxHeight = max(heightL, heightR)
return maxHeight + 1
def nodesOnLevel(root, level, result=[]):
if root is None:
return result
if level == 1:
result.append(root.val)
elif level > 1:
nodesOnLevel(root.left, level-1, result)
nodesOnLevel(root.right, level-1, result)
return result
def levelOrder_noQueue(root):
if root is None:
return
levels = height(root)
results = []
for i in range(1, levels+1):
results.append(nodesOnLevel(root, i))
return results;
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
print(levelOrder_noQueue(root))
# output is [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]]
# desired output is [[1], [2,3], [4,5]]
The problem is with result: there is only one result list in your code:
def nodesOnLevel(root, level, result=[]):
The default for result gets evaluated only once, when the script it parsed, not when the function is called. This means that when the levelOrder_noQueue function calls this function a second time (without passing a third argument), result will remain what it was, and the second call will just keep using it to append more elements.
So change this code to something like this:
def nodesOnLevel(root, level, result=None):
if result is None:
result = []
Or, otherwise, don't provide a default, and let the caller provide the empty list:
def nodesOnLevel(root, level, result):
And in levelOrder_noQueue:
results.append(nodesOnLevel(root, i, []))

Find paths in a binary tree from root to leave what sum, s not working as I expected

I am looking to get
Tree paths with required_sum 23: [[12, 7, 4], [12, 1, 10]]
But instead I am getting
Tree paths with required_sum 23: [[], []]
my code is as follows...
def findPaths(root, val):
allPaths = []
findPathsHelper(root, sum, [], allPaths)
return allPaths
def findPathsHelper(currentNode, sum, currentPath, allPaths):
if currentNode is None:
return
currentPath.append(currentNode.val)
if currentNode.val == sum and currentNode.left is None and currentNode.right is None:
allPaths.append(currentPath)
else:
findPathsHelper(currentNode.left, sum - currentNode.val, currentPath, allPaths)
findPathsHelper(currentNode.right, sum - currentNode.val, currentPath, allPaths)
del currentPath[-1]
If I change the line where allPaths.append(currentPath) to allPaths.append(list(currentPath)) I get the correct answer but I do not know why.
If I have the following...
l1 = []
l2 = [1, 2, 3]
l1.append(l2)
l1.append(l2)
# then l1 == [[1, 2, 3], [1, 2, 3]]
And if I do this instead...
l1 = []
l2 = [1, 2, 3]
l1.append(list(l2))
l1.append(list(l2))
# then l1 == [[1, 2, 3], [1, 2, 3]]
In which case both are the same, so I do not know why in my code it does not return the correct thing
I think your problem is you are deleting current path item before copying it. Python lists are “pass-by-object-reference”
del currentPath[-1]
also findPathsHelper(root, val, []) should be used instead of findPathsHelper(root, sum, [], allPaths) you are not passing the value .
to test things , i created a demo project and used a global variable and seems like it is working then.
class Node(object):
def __init__(self, value,left=None, right=None):
self.val = value
self.left = left
self.right = right
def __str__(self):
return '{:d} {:d}'.format(self.value)
allPaths=[]
def findPaths(root, val):
global allPaths
allPaths=[]
findPathsHelper(root, val, [])
return allPaths
def findPathsHelper(currentNode, sum, currentPath):
if currentNode is None:
return
currentPath.append(currentNode.val)
if currentNode.val == sum and currentNode.left is None and currentNode.right is None:
global allPaths
allPaths.append(currentPath.copy())
else:
findPathsHelper(currentNode.left, sum - currentNode.val, currentPath)
findPathsHelper(currentNode.right, sum - currentNode.val, currentPath)
del currentPath[-1]
if __name__ == "__main__":
root=Node(12,Node(7,Node(4),Node(10)),Node(1,Node(9),Node(10)))
result=findPaths(root,23)
print(allPaths)

Optimization 8-puzzle

I'm junior programmer, I am trying to solve 8-puzzle problem with breadth first search, but it took too long time to solve it, i want to optimize my code.
Configuration: [[5, 4, 3], [0, 7, 2], [6, 1, 8]] is going to solve in 22.623718615 seconds,
configuration [[8, 0, 6], [5, 4, 7], [2, 3, 1]] took among 235.721346421 seconds.
I want to decrease solve time.
There is my code:
from copy import deepcopy
from collections import deque
from time import perf_counter
most_hard = [[8, 0, 6], [5, 4, 7], [2, 3, 1]] # 30 moves
class CheckPuzzle:
def __init__(self, puzzle: list):
self.puzzle = puzzle
self.len = len(puzzle)
if self.len == 3:
self.goal = [[1, 2, 3],
[4, 5, 6],
[7, 8, 0]]
elif self.len == 4:
self.goal = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
if not self.is_valid():
raise TypeError("Puzzle is not valid")
elif not self.is_solvable():
raise Exception("Unsolvable puzzle")
# создай ф-ию check
def sum_of_numbers(self) -> int:
return sum(self.convert_to_1d(self.goal))
def sum_of_squares(self) -> int:
return sum([i ** 2 for i in self.convert_to_1d(self.goal)])
def is_valid(self) -> bool:
sum_of_numbers = 0
sum_of_squares = 0
for row in range(self.len):
for column in range(self.len):
sum_of_numbers += self.puzzle[row][column]
sum_of_squares += (self.puzzle[row][column]) ** 2
return sum_of_numbers == self.sum_of_numbers() and sum_of_squares == self.sum_of_squares()
def convert_to_1d(self, board) -> list:
one_dimension_matrix = []
for row in range(self.len):
for column in range(self.len):
one_dimension_matrix.append(board[row][column])
return one_dimension_matrix
def inversion(self, board) -> int:
inversion = 0
one_dimension_matrix = self.convert_to_1d(board)
for index in range(len(one_dimension_matrix)):
temp = one_dimension_matrix[index]
if temp == 0 or temp == 1:
continue
for elem in one_dimension_matrix[index:]:
if elem == 0:
continue
if temp > elem:
inversion += 1
return inversion
def is_solvable(self) -> bool:
inv_of_matrix = self.inversion(self.puzzle)
inv_of_goal_matrix = self.inversion(self.goal)
return (inv_of_matrix % 2 == 0 and inv_of_goal_matrix % 2 == 0) or \
(inv_of_matrix % 2 == 1 and inv_of_goal_matrix % 2 == 1)
class Puzzle:
def __init__(self, board: list):
self.board = board
self.len = len(board)
if self.len == 3:
self.goal = [[1, 2, 3],
[4, 5, 6],
[7, 8, 0]]
elif self.len == 4:
self.goal = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
def print_matrix(self) -> str:
output = ''
for row in self.board:
for elem in row:
output += str(elem) + " "
output += '\n'
return output
def get_index(self, matrix, value) -> tuple:
for i in range(self.len):
for j in range(self.len):
if matrix[i][j] == value:
return i, j
def manhattan(self):
distance = 0
for i in range(self.len):
for j in range(self.len):
if self.board[i][j] != 0:
x, y = divmod(self.board[i][j] - 1, self.len)
distance += abs(x - i) + abs(y - j)
return distance
def list_of_possible_moves(self) -> list:
x, y = self.get_index(self.board, 0)
possible_moves = []
if x > 0:
possible_moves.append((x - 1, y))
if x < self.len - 1:
possible_moves.append((x + 1, y))
if y > 0:
possible_moves.append((x, y - 1))
if y < self.len - 1:
possible_moves.append((x, y + 1))
return possible_moves
def move(self, to: tuple) -> list:
moving_board = deepcopy(self.board)
x, y = self.get_index(self.board, 0)
i, j = to
moving_board[x][y], moving_board[i][j] = moving_board[i][j], moving_board[x][y]
return moving_board
def solved(self) -> bool:
return self.board == self.goal
def __str__(self) -> str:
return ''.join(map(str, self))
def __iter__(self):
for row in self.board:
yield from row
class Node:
def __init__(self, puzzle, parent=None):
self.puzzle = puzzle
self.parent = parent
if self.parent:
self.g = parent.g + 1
else:
self.g = 0
def state(self) -> str:
return str(self)
def path(self):
node, p = self, []
while node:
p.append(node)
node = node.parent
yield from reversed(p)
def solved(self) -> bool:
return self.puzzle.solved()
def pretty_print(self) -> str:
return self.puzzle.print_matrix()
def h(self) -> int:
return self.puzzle.manhattan()
def f(self) -> int:
return self.h() + self.g
def all_moves(self) -> list:
return self.puzzle.list_of_possible_moves()
def __str__(self) -> str:
return str(self.puzzle)
def make_a_move(self, to: tuple) -> list:
return self.puzzle.move(to)
class GameTree:
def __init__(self, root):
self.root = root
def solve(self):
queue = deque([Node(self.root)])
seen = set()
seen.add(queue[0].state())
while queue:
queue = deque(sorted(list(queue), key=lambda node: node.f()))
node = queue.popleft()
if node.solved():
return node.path()
for move in node.all_moves():
moved = node.make_a_move(move)
child = Node(Puzzle(moved), node)
if child.state() not in seen:
queue.append(child)
seen.add(child.state())
def main():
a = [[5, 4, 3], [0, 7, 2], [6, 1, 8]]
c = Puzzle(a)
d = GameTree(c)
tic = perf_counter()
p = d.solve()
toc = perf_counter()
step = 0
for i in p:
print(i.pretty_print())
step += 1
print(step)
print(toc-tic)
if __name__ == "__main__":
main()
Description:
The 15-puzzle (also called Gem Puzzle, Boss Puzzle, Game of Fifteen,
Mystic Square and many others) is a sliding puzzle that consists of a
frame of numbered square tiles in random order with one tile missing.
The puzzle also exists in other sizes, particularly the smaller
8-puzzle. If the size is 3×3 tiles, the puzzle is called the 8-puzzle
or 9-puzzle, and if 4×4 tiles, the puzzle is called the 15-puzzle or
16-puzzle named, respectively, for the number of tiles and the number
of spaces. The object of the puzzle is to place the tiles in order by
making sliding moves that use the empty space.
https://en.wikipedia.org/wiki/15_puzzle

Categories