Implement the Tower of Hanoi Algorithm - Implement the Tower of Hanoi Algorithm

Tell us what’s happening:

I have already implemented all the logic and it works: all the tests pass except the return str one. I checked why it says that it’s not a string, but I found no solution, why is that?

Your code so far

from copy import deepcopy

def create_start(disks: int) -> dict:
        tower_1 = [x for x in range(disks + 1) if x > 0]
        tower_1.reverse()
        towers = [tower_1,[],[]]
        return {"0:0" : towers}

def create_solution(disks: int) -> list:
        tower_1 = [x for x in range(disks + 1) if x > 0]
        tower_1.reverse()
        return [[],[],tower_1]

class Towers:
    def __init__(self, disks: int) -> None:
        self.disks = disks
        self.arrangements = create_start(disks)
        self.solution = create_solution(disks)

        self.current_child = 0

        self.next_child = 1

        self.parents = {
            0: 0
        }

        self.visited = set()

        self.visited.add(
            self.state_to_tuple(self.arrangements["0:0"])
        )

    @property
    def disks(self):
        return self._disks

    @disks.setter
    def disks(self, new_disks):
        self._disks = new_disks

    @property
    def arrangements(self):
        return self._arrangements

    @arrangements.setter
    def arrangements(self, new_arrangements):
        self._arrangements = new_arrangements

    def state_to_tuple(self, arrangement):
        return tuple(tuple(rod) for rod in arrangement)

    def read_keys(self, key: str) -> tuple:
        return tuple(key.split(":"))

    def search_child(self, child: int) -> str:
        for key in self.arrangements:
            current_key = self.read_keys(key)

            if current_key[1] == str(child):
                return key

        raise KeyError(
            f"Key with child {child} not found!"
        )

    def create_key(self, parent: int, child: int) -> str:
        return str(parent) + ":" + str(child)

    def last_child(self):
        return self.next_child - 1

    def add_arrangement(self, new_arrangement, parent):
        child = self.next_child

        key = self.create_key(parent, child)

        self.arrangements[key] = new_arrangement

        self.parents[child] = parent

        self.visited.add(
            self.state_to_tuple(new_arrangement)
        )

        self.next_child += 1

    def create_arrangements(self):
        solution = self.state_to_tuple(self.solution)

        while solution not in self.visited:

            curr_child = self.current_child

            parent = self.parents[curr_child]

            curr_key = self.create_key(
                parent,
                curr_child
            )

            arrangement = self.arrangements[curr_key]

            for rod_n, rod in enumerate(arrangement):

                if not rod:
                    continue

                disk = rod[-1]

                for other_rod_n, other_rod in enumerate(arrangement):

                    if rod_n == other_rod_n:
                        continue

                    if other_rod and other_rod[-1] < disk:
                        continue

                    new_arrangement = deepcopy(arrangement)

                    new_arrangement[rod_n].pop()
                    new_arrangement[other_rod_n].append(disk)

                    state = self.state_to_tuple(
                        new_arrangement
                    )

                    if state not in self.visited:
                        self.add_arrangement(
                            new_arrangement,
                            curr_child
                        )

            self.current_child += 1    

def hanoi_solver(disks: int) -> str:
    towers = Towers(disks)
    towers.create_arrangements()

    solution_key = ""

    for key in towers.arrangements:
        if towers.arrangements[key] == towers.solution:
            solution_key = key
            break

    path = []
    current_key = solution_key

    while True:
        path.append(current_key)

        parent, child = towers.read_keys(current_key)

        if parent == child:
            break

        current_key = towers.search_child(int(parent))

    path.reverse()

    result = ""

    for key in path:
        arrangement = towers.arrangements[key]
        result += str(arrangement[0])
        result += " "
        result += str(arrangement[1])
        result += " "
        result += str(arrangement[2])
        
        if key != path[-1]:
            result += "\n"

    return result                    

try:
    print(hanoi_solver(3))
except KeyError as e:
    print(f"KeyError: {e}")

Your browser information:

User Agent is: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:153.0) Gecko/20100101 Firefox/153.0

Challenge Information:

Implement the Tower of Hanoi Algorithm - Implement the Tower of Hanoi Algorithm

GitHub Link: freeCodeCamp/curriculum/challenges/english/blocks/lab-tower-of-hanoi/68773ee26f332a80bc0295db.md at main · freeCodeCamp/freeCodeCamp · GitHub

When I test your code:

Traceback (most recent call last):
  File "main.py", line 42, in <module>
  File "main.py", line 2, in hanoi_solver
NameError: name 'Towers' is not defined

Sorry, I posted only the hanoi_solverfunction and not the actual solver class, now is updated

Judging by the tests which fail, I think it’s timing out. It takes a bit long to run the other test case.

What result do you get for this?

print(hanoi_solver(11))

This is something test 3 checks. I’m not getting a result I think it’s hung or timed out.

Test 8 checks up to hanoi_solver(9) which I am able to get a result for, but I think the test is timing out.

Here’s how long each function takes to return:

hanoi_solver(5)
0.03s

hanoi_solver(6)
0.10s

hanoi_solver(7)
0.37s

hanoi_solver(8)
1.40s

hanoi_solver(9)
6.10s

hanoi_solver(10)
31.16s

hanoi_solver(11) might take 2.5 min or longer

EDIT: ok got it:

hanoi_solver(11)
162.62s

I tried to optimize all the search loops inside the dictionary and remove all unnecessary searches and it improved a lot, it returned hanoi of 11 in 8.12 sec (for the time I used a reference for benchmarking online) but the error is still there.

from copy import deepcopy
import time


def create_start(disks: int) -> dict:
    tower_1 = [x for x in range(disks + 1) if x > 0]
    tower_1.reverse()

    towers = [tower_1, [], []]

    return {"0:0": towers}


def create_solution(disks: int) -> list:
    tower_1 = [x for x in range(disks + 1) if x > 0]
    tower_1.reverse()

    return [[], [], tower_1]


class Towers:
    def __init__(self, disks: int) -> None:
        self.disks = disks
        self.arrangements = create_start(disks)
        self.solution = create_solution(disks)
        self.current_child = 0
        self.next_child = 1

        self.parents = {
            0: 0
        }

        self.nodes = {
            0: self.arrangements["0:0"]
        }

        self.visited = {
            self.state_to_tuple(self.arrangements["0:0"])
        }

        self.solution_child = None

    @property
    def disks(self):
        return self._disks

    @disks.setter
    def disks(self, new_disks):
        self._disks = new_disks

    @property
    def arrangements(self):
        return self._arrangements

    @arrangements.setter
    def arrangements(self, new_arrangements):
        self._arrangements = new_arrangements

    def state_to_tuple(self, arrangement):
        return tuple(
            tuple(rod)
            for rod in arrangement
        )

    def read_keys(self, key: str) -> tuple:
        return tuple(key.split(":"))

    def search_child(self, child: int) -> str:
        parent = self.parents[child]

        return self.create_key(parent, child)

    def create_key(self, parent: int, child: int) -> str:
        return str(parent) + ":" + str(child)

    def last_child(self):
        return self.next_child - 1

    def add_arrangement(self, new_arrangement, parent):
        child = self.next_child

        key = self.create_key(parent, child)

        self.arrangements[key] = new_arrangement

        self.nodes[child] = new_arrangement

        self.parents[child] = parent

        self.visited.add(
            self.state_to_tuple(new_arrangement)
        )

        self.next_child += 1

        return child

    def create_arrangements(self):
        solution = self.state_to_tuple(self.solution)

        while self.current_child < self.next_child:

            curr_child = self.current_child

            arrangement = self.nodes[curr_child]

            for rod_n, rod in enumerate(arrangement):

                if not rod:
                    continue

                disk = rod[-1]

                for other_rod_n, other_rod in enumerate(arrangement):

                    if rod_n == other_rod_n:
                        continue

                    if other_rod and other_rod[-1] < disk:
                        continue

                    new_arrangement = deepcopy(arrangement)

                    new_arrangement[rod_n].pop()
                    new_arrangement[other_rod_n].append(disk)

                    state = self.state_to_tuple(
                        new_arrangement
                    )

                    if state in self.visited:
                        continue

                    child = self.add_arrangement(
                        new_arrangement,
                        curr_child
                    )

                    if state == solution:
                        self.solution_child = child
                        return

            self.current_child += 1


def hanoi_solver(disks: int) -> str:
    towers = Towers(disks)

    towers.create_arrangements()

    current_child = towers.solution_child

    path = []

    while True:

        current_parent = towers.parents[current_child]

        current_key = towers.create_key(
            current_parent,
            current_child
        )

        path.append(current_key)

        if current_child == 0:
            break

        current_child = current_parent

    path.reverse()

    result = []

    for key in path:
        arrangement = towers.arrangements[key]

        result.append(
            str(arrangement[0])
            + " "
            + str(arrangement[1])
            + " "
            + str(arrangement[2])
        )

    return "\n".join(result)


try:
    start_time = time.perf_counter()

    solution = hanoi_solver(11)

    end_time = time.perf_counter()

    print(solution)
    print(f"\nTime required to solve Hanoi(11): "
          f"{end_time - start_time:.6f} seconds")

except KeyError as e:
    print(f"KeyError: {e}")

Good improvement by my tests as well:

hanoi_solver(9)
2.67s

hanoi_solver(10)
8.55s

hanoi_solver(11)
27.53s

Actually it now passes test 8, so you’ve resolved one test.

Test 3 is the only one that checks hanoi_solver(11). It’s obviously returning a string so I think that it’s still timing out on hanoi_solver(11). Test 8 only goes up to hanoi_solver(9) which returns in under 3s now.

I’ve added this code to test:

def hanoi_solver(disks: int) -> str:
    import time
    start = time.time()
    print(f"hanoi_solver({disks})")
...
...

    end = time.time()
    print(end - start)
    return "\n".join(result)

This problem kind of lends itself to a recursive solution, I think you would have better luck trying a solution that uses recursion. I think that might be the intended way, since you are coming up with the correct solutions but having a timeout problem.

Yes, it solved everything and it was much faster, thanks for helping