Search code examples
pythonpython-3.xfunctiontowers-of-hanoinon-recursive

Towers of Hanoi non-recursive function


I'm trying to figure out how to implement a non-recursive algorithm for the Hanoi Towers problem in function hanoi_2 below, but I have no idea how to continue...

It throws an error: "can't pop from empty list". It works somehow when I input an odd number, however, when the third turn passes things go wrong. When an even number is input as the number of discs the program doesn't even start.

What is wrong?

from turtle import *
from tkinter import *  # used for the dialog box
from tkinter.simpledialog import askinteger  # used for the dialog box
from tkinter import ttk  # used for the progress bar
import time  # used for time-related functions (in pause)
import pickle  # used to save an object to a file

class Disc(Turtle):
    def __init__(self, n):
        Turtle.__init__(self, shape="square", visible=False)
        self.pu()
        self.shapesize(1.5, n*1.5, 2)  # square-->rectangle
        self.fillcolor(n/10., 0, 1-n/10.)
        self.st()
        self.speed(11-n)  # sets the speed of movement of the rectangles (the bigger, the slower)
        self.moves = 0  # stores the number of times the disc is moved

class Tower(list):
    """Hanoi tower, a subclass of built-in type list"""
    def __init__(self, x):
        """create an empty tower. x is x-position of peg"""
        self.x = x

    def push(self, d):
        d.setx(self.x)
        d.sety(-150+34*len(self))
        d.clear()
        d.write("Moved %s times" %(str(d.moves)), align="left", font=("Courier", 16, "bold"))
        d.moves += 1  # increments the number of moves each time the disc is moved
        self.append(d)

    def pop(self):
        d = list.pop(self)
        d.sety(150)
        return d

def hanoi(n, from_, with_, to_):
    global moves
    global ans
    clear()
    if n > 0:
        hanoi(n-1, from_, to_, with_)
        moves += 1  # amount of total moves is incremented
        to_.push(from_.pop())
        hanoi(n-1, with_, from_, to_)
    sety(-255)
    write("Total moves: %s" % (moves), align="center", font=("Courier", 16, "bold"))
    sety(-320)
    progress_bar(ans)  # calls progress bar function

def hanoi_2(n, A, B, C):
    global moves
    clear()
    if n%2==0:
        B=C
        C=A
        A=B
    for i in range(1,(2**n)-1):
        if i%3==1:
            C.push(A.pop())
        if i%3==2:
            B.push(A.pop())
        if i%3==0:
            B.push(C.pop())

Solution

  • The issue with the even number of discs is that the stack swap is not right: you seem to want to cycle the three stacks (which you do in a wrong way as you lose the reference to the original B list), while actually you need to swap only the B and C stacks, which you can do as follows:

    B, C = C, B
    

    The issue with the algorithm is that although you have the involved two stacks right (based on i%3), you must still determine which of the two involved stacks is the giver and which one the taker, as this is not always the same! It is probably best if you write a function for this that takes two stacks and determines which of the two possible "directions" is a valid one and then performs that move:

    def validMove(A, B):
        if not len(A): 
            A.push(B.pop())
        elif not len(B):
            B.push(A.pop())
        elif A[-1] > B[-1]:
            A.push(B.pop())
        else:
            B.push(A.pop())
    

    Now the main algorithm will look like this:

    for i in range(1,2**n):
        if i%3==1:
            validMove(A, C)
        if i%3==2:
            validMove(A, B)
        if i%3==0:
            validMove(B, C)