Tower of Hanoi recursive algorithm for four towers in Python - python

I'm having a decent amount of trouble implementing the optimal algorithm for the tower of hanoi game for four stools in python.
First, in class we solved the tower of hanoi algorithm for three towers, for which we were given the following algorithm:
Move all but the bottom disk from source to intermediate tower
Move the bottom disk form the source to the destination
Move all but the bottom disk from intermediate stool to destination tower
The actual code for this algorithm (with model being of class Model which has towers and disks, with a move method that moves disks from one tower to another) yields:
def move_disks(n, source, intermediate, destination):
"""Move n disks from source to destination
#param int n:
#param int source:
#param int intermediate:
#param int destination:
#rtype: None
"""
if n > 1:
move_disks(n - 1, source, destination, intermediate)
move_disks(1, source, intermediate, destination)
move_disks(n - 1, intermediate, source, destination)
else:
model.move(source, destination)
Now for four stools, I'm given the following:
Move n- i disks to intermediate stool using all four towers
Move i disks from the original tower to the destination tower, using only three available towers
Move n-i smallest disks from intermediate tower to destination tower
Manually playing around with disks and towers I got n-i = 2 if n>=3 and i = 1 if n =2. Since there are 4 possible sources and destinations, in my own function I have 5 arguments instead of 4:
def move_disks(n, source, intermediate, intermediate2, destination):
"""Move n disks from source to destination
#param int n:
#param int source:
#param int intermediate:
#param int intermediate2:
#param int destination:
#rtype: None
"""
if n > 1:
move_disks(n - i, source, intermediate 2 destination, intermediate)
move_disks(1, source, intermediate, intermediate2, destination)
move_disks(n - i, intermediate, intermediate2, source, destination)
else:
print("{} -> {}".format(source,destination)) # used to explicitly follow output in the console
#model.move(source, destination) --> to be implemented when the function returns the right output
When I run it for n=3 I get:
1 -> 3
1 -> 2
3 -> 2
1 -> 4
2 -> 3
2 -> 4
3 -> 4
which gives the same number as moves as the solution for three towers.
The optimal solution for four stools should yield:
1 -> 3
1 -> 2
1 -> 4
2 -> 4
3 -> 4
The problem is definitely coming from my understanding of the algorithm so I've tried tracing function calls on a dry erasable board for a few hours to no avail.
Any tips or tricks for what else I should be doing or looking for to solve this algorithm? I'm honestly lost and a little bit discouraged.

I see two issues with your code.
The first is that you're using a variable i without defining it in the function. It probably has a global value in your environment, but perhaps not an appropriate one for the given n. You should probably have your function figure out what i should be and assign it as a local variable.
The second issue is that you're always recursing using the same four-tower function, while the algorithm you describe is supposed to use only three towers in the middle step. This probably means you should keep around your original function (in the first code block), and use a different name for your four-tower function.
If we name your first function move_disks3 and the second move_disks4, we can make it work:
def move_disks3(n, source, intermediate, destination):
"""Move n disks from source to destination
#param int n:
#param int source:
#param int intermediate:
#param int destination:
#rtype: None
"""
if n > 1:
move_disks3(n - 1, source, destination, intermediate)
move_disks3(1, source, intermediate, destination)
move_disks3(n - 1, intermediate, source, destination)
else:
print("{} -> {}".format(source,destination))
def move_disks4(n, source, intermediate, intermediate2, destination):
"""Move n disks from source to destination
#param int n:
#param int source:
#param int intermediate:
#param int intermediate2:
#param int destination:
#rtype: None
"""
if n > 1:
if n > 2: # I'm not sure this picks the optimal i in all cases, but it does for n=3
i = 2
else:
i = 1
move_disks4(n - i, source, intermediate2, destination, intermediate)
move_disks3(i, source, intermediate2, destination)
move_disks4(n - i, intermediate, intermediate2, source, destination)
else:
print("{} -> {}".format(source,destination))
I'm not certain I understood your statements about the optimal i value, so you might need to make a few changes if I got something wrong there. But this code does work, and does give the desired results for n = 3 (and plausible results for a few higher n values I tested):
>>> move_disks4(3, 1, 2, 3, 4)
1 -> 2
1 -> 3
1 -> 4
3 -> 4
2 -> 4

def move4Poles(begin, end, temp1, temp2, disks):
if disks == 1:
print(begin, "to", end)
return
if disks == 2:
move4Poles(begin, temp1, '_', '_', 1)
move4Poles(begin, end, '_', '_', 1)
move4Poles(temp1, end, '_', '_', 1)
return
if disks >= 3:
move4Poles(begin, temp2, temp1, end, disks - 2)
move4Poles(begin, end, temp1, '_', 2)
move4Poles(temp2, end, temp1, begin, disks - 2)
move4Poles('A', 'D', 'B', 'C', 4)
A to B
A to C
B to C
A to B
A to D
B to D
C to B
C to D
B to D

Related

Why this recursive code treats its input int argument differently when it is 2 that any other number?

I'm trying to understand how the tower-of-hanoi code mechanics.
I reduced the full code to this piece of code to understand its behviour:
def hanoi(disks, source, helper, destination):
if (disks == 1):
print('Disk {} moves from tower {} to tower {}.'.format(disks, source, destination))
return
hanoi(disks - 1, source, destination, helper)
disks = 3
hanoi(disks, 'A', 'B', 'C')
Can someone please explain why I get different outputs when disks is equals to 3 versus 2?
The output for disks = 3: Disk 1 moves from tower A to tower C.
The output for disks = 2: Disk 1 moves from tower A to tower B.
Thanks!
I would expect the funtion hanoi(disks - 1, source, destination, helper) to output the first line in both cases when the input disks is 3 and 2, but for some reason it is executed only when disks = 2 and not for any other value.
def hanoi(disks, source, helper, destination):
if (disks == 1):
print('Disk {} moves from tower {} to tower {}.'.format(disks, source, destination))
return
hanoi(disks - 1, source, destination, helper)
print('Disk {} moves from tower {} to tower {}.'.format(disks, source, destination))
hanoi(disks - 1, helper, source, destination)
This modified function will print out the individual moves for each disk, and you'll see the expected output for disks equals to 2 and 3.

Faster way to add dummy nodes in networkx to limit degree

I am wondering if I can speed up my operation of limiting node degree using an inbuilt function.
A submodule of my task requires me to limit the indegree to 2. So, the solution I proposed was to introduce sequential dummy nodes and absorb the extra edges. Finally, the last dummy gets connected to the children of the original node. To be specific if an original node 2 is split into 3 nodes (original node 2 & two dummy nodes), ALL the properties of the graph should be maintained if we analyse the graph by packaging 2 & its dummies into one hypothetical node 2'; The function I wrote is shown below:
def split_merging(G, dummy_counter):
"""
Args:
G: as the name suggests
dummy_counter: as the name suggests
Returns:
G with each merging node > 2 incoming split into several consecutive nodes
and dummy_counter
"""
# we need two copies; one to ensure the sanctity of the input G
# and second, to ensure that while we change the Graph in the loop,
# the loop doesn't go crazy due to changing bounds
G_copy = nx.DiGraph(G)
G_copy_2 = nx.DiGraph(G)
for node in G_copy.nodes:
in_deg = G_copy.in_degree[node]
if in_deg > 2: # node must be split for incoming
new_nodes = ["dummy" + str(i) for i in range(dummy_counter, dummy_counter + in_deg - 2)]
dummy_counter = dummy_counter + in_deg - 2
upstreams = [i for i in G_copy_2.predecessors(node)]
downstreams = [i for i in G_copy_2.successors(node)]
for up in upstreams:
G_copy_2.remove_edge(up, node)
for down in downstreams:
G_copy_2.remove_edge(node, down)
prev_node = node
G_copy_2.add_edge(upstreams[0], prev_node)
G_copy_2.add_edge(upstreams[1], prev_node)
for i in range(2, len(upstreams)):
G_copy_2.add_edge(prev_node, new_nodes[i - 2])
G_copy_2.add_edge(upstreams[i], new_nodes[i - 2])
prev_node = new_nodes[i - 2]
for down in downstreams:
G_copy_2.add_edge(prev_node, down)
return G_copy_2, dummy_counter
For clarification, the input and output are shown below:
Input:
Output:
It works as expected. But the problem is that this is very slow for larger graphs. Is there a way to speed this up using some inbuilt function from networkx or any other library?
Sure; the idea is similar to balancing a B-tree. If a node has too many in-neighbors, create two new children, and split up all your in-neighbors among those children. The children have out-degree 1 and point to your original node; you may need to recursively split them as well.
This is as balanced as possible: node n becomes a complete binary tree rooted at node n, with external in-neighbors at the leaves only, and external out-neighbors at the root.
def recursive_split_node(G: 'nx.DiGraph', node, max_in_degree: int = 2):
"""Given a possibly overfull node, create a minimal complete
binary tree rooted at that node with no overfull nodes.
Return the new graph."""
global dummy_counter
current_in_degree = G.in_degree[node]
if current_in_degree <= max_in_degree:
return G
# Complete binary tree, so left gets 1 more descendant if tied
left_child_in_degree = (current_in_degree + 1) // 2
left_child = "dummy" + str(dummy_counter)
right_child = "dummy" + str(dummy_counter + 1)
dummy_counter += 2
G.add_node(left_child)
G.add_node(right_child)
old_predecessors = list(G.predecessors(node))
# Give all predecessors to left and right children
G.add_edges_from([(y, left_child)
for y in old_predecessors[:left_child_in_degree]])
G.add_edges_from([(y, right_child)
for y in old_predecessors[left_child_in_degree:]])
# Remove all incoming edges
G.remove_edges_from([(y, node) for y in old_predecessors])
# Connect children to me
G.add_edge(left_child, node)
G.add_edge(right_child, node)
# Split children
G = recursive_split_node(G, left_child, max_in_degree)
G = recursive_split_node(G, right_child, max_in_degree)
return G
def clean_graph(G: 'nx.DiGraph', max_in_degree: int = 2) -> 'nx.DiGraph':
"""Return a copy of our original graph, with nodes added to ensure
the max in degree does not exceed our limit."""
G_copy = nx.DiGraph(G)
for node in G.nodes:
if G_copy.in_degree[node] > max_in_degree:
G_copy = recursive_split_node(G_copy, node, max_in_degree)
return G_copy
This code for recursively splitting nodes is quite handy and easily generalized, and intentionally left unoptimized.
To solve your exact use case, you could go with an iterative solution: build a full, complete binary tree (with the same structure as a heap) implicitly as an array. This is, I believe, the theoretically optimal solution to the problem, in terms of minimizing the number of graph operations (new nodes, new edges, deleting edges) to achieve the constraint, and gives the same graph as the recursive solution.
def clean_graph(G):
"""Return a copy of our original graph, with nodes added to ensure
the max in degree does not exceed 2."""
global dummy_counter
G_copy = nx.DiGraph(G)
for node in G.nodes:
if G_copy.in_degree[node] > 2:
predecessors_list = list(G_copy.predecessors(node))
G_copy.remove_edges_from((y, node) for y in predecessors_list)
N = len(predecessors_list)
leaf_count = (N + 1) // 2
internal_count = leaf_count // 2
total_nodes = leaf_count + internal_count
node_names = [node]
node_names.extend(("dummy" + str(dummy_counter + i) for i in range(total_nodes - 1)))
dummy_counter += total_nodes - 1
for i in range(internal_count):
G_copy.add_edges_from(((node_names[2 * i + 1], node_names[i]), (node_names[2 * i + 2], node_names[i])))
for leaf in range(internal_count, internal_count + leaf_count):
G_copy.add_edge(predecessors_list.pop(), node_names[leaf])
if not predecessors_list:
break
G_copy.add_edge(predecessors_list.pop(), node_names[leaf])
if not predecessors_list:
break
return G_copy
From my testing, comparing performance on very dense graphs generated with nx.fast_gnp_random_graph(500, 0.3, directed=True), this is 2.75x faster than the recursive solution, and 1.75x faster than the original posted solution. The bottleneck for further optimizations is networkx and Python, or changing the input graphs to be less dense.

Find a path between vertices, with at most k steps between any two nodes with food

I am trying to build a graph using Python. For every vertex, there is additional information about whether sufficient food is available at that vertex.
class Vertex:
"""
Vertex represents a location in the quokka's quest to find food.
It contains the relevant information surrounding the location.
Attributes:
* self.has_food (bool) - indicates whether this location has food.
* self.edges (List[Vertex]) - list of connected vertices.
"""
def __init__(self, has_food: bool) -> None:
"""
Initialises this vertex, by setting the attribute whether it has food.
:param has_food - boolean indicating whether this location has food.
"""
self.has_food = has_food
self.edges = []
In the graph class, I am trying to build a function called find_path(s, t, k); this operation consists of finding a path from vertex s to vertex t such that from any location with food along this path we reach the next location with food in at most k steps.
I was trying to find all paths, but I get stuck on how to check whether "from any location with food along this path we reach the next location with food in at most k steps."
For the graph class, I already have some operations defined. They are:
add_vertex(v) -> bool
Adds the Vertex v to the graph, returning True if the operation was successful, False if it was not, or it was invalid.
fix_edge(u, v) -> bool
Fixes an edge between two vertices, u and v. If an edge already exists, or the edge is invalid, then this operation should return False. Else, if the operation succeeds, return True.
Example: If an edge between u and v already exists or is invalid, fix_edge(u, v) should return False.
block_edge(u, v) -> bool
Blocks the edge between two vertices, u and v. Removes the edge if it exists, and returns True if the operation was successful. If the edge does not exist or is invalid, it should be unsuccessful and return False.
My code for find_path(s, t, k) looks like this and it's incomplete.
def find_path(self, s: Vertex, t: Vertex, k: in) -> Union[List[Vertex], None]:
"""
find_path returns a SIMPLE path between `s` and `t` such that from any
location with food along this path we reach the next location with food
in at most `k` steps
:param s - The start vertex for the quokka colony
:param t - The destination for the quokka colony
:param k - The maximum number of hops between locations with food, so
that the colony can survive!
:returns
* The list of vertices to form the simple path from `s` to `t`
satisfying the conditions.
OR
* None if no simple path exists that can satisfy the conditions, or
is invalid.
Example:
(* means the vertex has food)
* *
A---B---C---D---E
1/ find_path(s=A, t=E, k=2) -> returns: [A, B, C, D, E]
2/ find_path(s=A, t=E, k=1) -> returns: None
(because there isn't enough food!)
3/ find_path(s=A, t=C, k=4) -> returns: [A, B, C]
"""
# TODO implement me please
if k < 0:
return False
else:
path = path + [s]
if s == t:
return [path]
if not graph.has_key(s):
return []
paths = []
for node in graph[s]:
if node not in path:
newpaths = find_all_paths(graph, node, t, path)
for newpath in newpaths:
paths.append(newpath)
return paths
Can someone help me figure out how to implement the path-finding algorithm?

Recursive function return value without allocating extra space?

So I'm stuck at trying to optimize this LeetCode problem called Frog Jump. Here's the basic description of the problem:
Given a list of stones' positions (in units) in sorted ascending order, determine if the frog is able to cross the river by landing on the last stone. Initially, the frog is on the first stone and assume the first jump must be 1 unit.
If the frog's last jump was k units, then its next jump must be either k - 1, k, or k + 1 units. Note that the frog can only jump in the forward direction.
e.g: [0,1,3,5,6,8,12,17]
There are a total of 8 stones. The first stone at the 0th unit, second
stone at the 1st unit, third stone at the 3rd unit, and so on... The
last stone at the 17th unit.
Return true. The frog can jump to the last stone by jumping 1 unit
to the 2nd stone, then 2 units to the 3rd stone, then 2 units to the
4th stone, then 3 units to the 6th stone, 4 units to the 7th stone,
and 5 units to the 8th stone.
Here's my solution which works. What I need help with is how to output the same boolean without allocating the extra res array which stores the logical OR of all the explored paths using DFS.
class Solution:
def canCross(self, stones: List[int]) -> bool:
if stones[1] != 1:
return False
res = []
memo = {}
def dfs(curr_stone, last_stone, last_jump):
if curr_stone == last_stone:
res.append(True)
return
if curr_stone > last_stone:
res.append(False)
return
for i in range(-1,2):
next_stone = curr_stone + (last_jump + i)
if next_stone in stones and next_stone > curr_stone and (next_stone, last_stone, last_jump+i) not in memo:
memo[(next_stone, last_stone, last_jump+i)] = 1
dfs(next_stone, last_stone, last_jump+i)
dfs(1, stones[-1], 1)
return any(res)
Can someone help me with how to approach these questions? I always struggle a lot with these sort of questions and end up storing values in an array; however, ideally I would like the result of the recursive code to be the same without allocating the extra res array space.
Since the entire purpose of the function seems to boil down to return any(res), it seems like you should return True/False from the recursive function instead of appending them, then exit from all recursive calls once a single True value is found, and not bother saving every found value.
That will involve checking what was returned from the recursive call dfs(next_stone, last_stone, last_jump+i), and if it's true, simply returning that True:
from typing import List
class Solution:
def canCross(self, stones: List[int]) -> bool:
if stones[1] != 1:
return False
memo = {}
def dfs(curr_stone, last_stone, last_jump):
if curr_stone == last_stone:
return True # Return the results instead of appending them to a list
if curr_stone > last_stone:
return False
for i in range(-1, 2):
next_stone = curr_stone + (last_jump + i)
if next_stone in stones and next_stone > curr_stone and (next_stone, last_stone, last_jump + i) not in memo:
memo[(next_stone, last_stone, last_jump + i)] = 1
rec_result = dfs(next_stone, last_stone, last_jump + i)
if rec_result: # Then check the recursive results at the call-site
return True
return dfs(1, stones[-1], 1)
I'll note, I haven't done extensive testing on this, but from some quick "head interpretation", it seems to be equivalent.

Maximum Path Sum between 2 Leaf Nodes(GeeksForGeeks)

Given a binary tree in which each node element contains a number. Find the maximum possible sum from one leaf node to another.
Example 1:
Input :
3
/ \
4 5
/ \
-10 4
Output: 16
Explanation :
Maximum Sum lies between leaf node 4 and 5.
4 + 4 + 3 + 5 = 16.
Example 2:
Input :
-15
/ \
5 6
/ \ / \
-8 1 3 9
/ \ \
2 -3 0
/ \
4 -1
/
10
Output : 27
Explanation:
The maximum possible sum from one leaf node
to another is (3 + 6 + 9 + 0 + -1 + 10 = 27)
This is the solution:
'''
# Node Class:
class Node:
def _init_(self,val):
self.data = val
self.left = None
self.right = None
'''
res = -999999999
def maxPathSumUtil(root):
global res
if root is None:
return 0
if root.left is None and root.right is None:
return root.data
ls=maxPathSumUtil(root.left)
rs=maxPathSumUtil(root.right)
if root.left and root.right:
res=max(res,ls+rs+root.data)
return max(ls+root.data,rs+root.data) #Line: Problem
if root.left is None:
return rs+root.data
else:
return ls+root.data
def maxPathSum(root):
global res
res = -999999999
maxPathSumUtil(root)
return res
Can anyone tell me why do we use return max(ls+root.data,rs+root.data). And if we do use return max(ls+root.data,rs+root.data) for checking the maximum value then why do we use res=max(res,ls+rs+root.data) and not just res = max(ls+root.data,rs+root.data).
EDIT:
For example:
Let's take this tree for example:
10
/ \
8 2
/ \
3 5
In this, after recursive calls, ls becomes 3 and rs becomes 5.
res becomes ls+rs+root.data which is 3+5+8 = 16.
Then return max(ls+root.data,rs+root.data) which is max(11,13) = 13.
Now after this according to me the function should just return 13 but that does not happen. Even though return is not a recursive statement. How is the control flow of the code happening?
There are two things that are measured in parallel during execution:
ls+rs+root.data is the max path in the tree rooted by root, between two of the leaves below it. So it is (the value) of a leaf-to-leaf path
The function return value is the maximum path from root to any of the leaves below it. So it is (the value) of a root-to-leaf path
These are two different concepts and should not be mixed up.
Both ls and rs are function return values: ls represents the maximum path from root.left to a leaf. And in the same way rs represents the maximum path from root.right to a leaf.
ls+rs+root.data on the other hand, represents a path from leaf to leaf passing through root.
res should be updated if that latter expression is greater than res, hence the max().
But the function's return value should not represent a leaf-to-leaf path, but a root-to-leaf path. So that is why we have:
return max(ls+root.data,rs+root.data)
This tells the caller what the maximum root-to-leaf path is, not what the maximum leaf-to-leaf path is. The latter is used for determining res, not the function's return value.
I hope this clarifies the distinction between these two concepts and the roles they play in the algorithm.
The example
You presented this tree as example:
10
/ \
8 2
/ \
3 5
Indeed, when the function is called for the node 8, it:
sets res to 16 (the max path between two leaves below the node)
returns 13 (the max path from the node to one of its leaves)
You then ask:
Now after this according to me the function should just return 13 but that does not happen.
But it does happen like that. You should however not forget that this is the return value of maxPathSumUtil, not of maxPathSum. Also, this is not the top-level call of maxPathSumUtil. The value 13 is returned to another execution context of maxPathSumUtil, where root is the node 10. Then -- after another recursive call is made (with root equal to node 2), this top-level execution of the function maxPathSumUtil will:
set res to 25 (the max path between two leaves below the node 10)
return 23 (the max path from the node 10 to one of its leaves)
This toplevel call was made from within maxPathSum, which ignores the value returned by maxPathSumUntil.
It only takes the value of res (25), and returns that:
maxPathSumUtil(root) # notice that return value is ignored.
return res
At each node, we have to check whether that node's left and right child resulted in max path. But when we return, we need to return either the left or right path depending upon whichever is max.
Let's take this tree for example:
10
/ \
8 2
/ \
3 5
In this, after recursive calls, ls becomes 3 and rs becomes 5. res becomes ls+rs+root.data which is 3+5+8 = 16. So res(result) will be updated to 16, and return will be max(11,13) which is 13. Now this 13 value will be used by node 10 as ls(left value).

Categories

Resources