Search code examples
pythonperformancerecursiontime

Is there a way to see results of my recursion code? (Some sort of website maybe)


I solved the today's (November 25th 2024) leetcode question and it was a BFS solution, but then I wanted to try recursion on the code and due to O(V!) time complexity, there is no way this code will return anything untill the heat dead of the universe (Something like 720! lol)

Here is the question: https://leetcode.com/problems/sliding-puzzle/description/

Is there a website or way to see or watch like a debugger so I can understand if the code is working as intended (even though it will never compile) ?

Here is my recursion code:


from typing import List, Tuple
class Solution:
    def slidingPuzzle(self, board: List[List[int]]) -> int:
        def to_string(board: List[List[int]]) -> str:
            """Convert the 2x3 board into a string representation."""
            return "".join(str(num) for row in board for num in row)
        
        def swap(state: str, i: int, j: int) -> str:
            """Swap characters at index i and j in the string."""
            state = list(state)
            state[i], state[j] = state[j], state[i]
            return "".join(state)

        def dfs(state: str, depth: int) -> int:
            if state == target:
                return depth
            
            if state in memo:
                return float('inf')  # State already visited
            
            memo.add(state)
            zero_idx = state.index('0')
            min_moves = float('inf')

            for neighbor in directions[zero_idx]:
                new_state = swap(state, zero_idx, neighbor)
                moves = dfs(new_state, depth + 1)
                min_moves = min(min_moves, moves)
            
            memo.remove(state)  # Backtrack
            return min_moves
        
        # Target state
        target = "123450"
        
        # Convert the board to a string
        start = to_string(board)

        # Directions based on 2x3 board
        directions = [
            [1, 3],    # Index 0
            [0, 2, 4], # Index 1
            [1, 5],    # Index 2
            [0, 4],    # Index 3
            [1, 3, 5], # Index 4
            [2, 4]     # Index 5
        ]

        # Memoization to track visited states
        memo = set()

        # Perform DFS
        result = dfs(start, 0)
        return result if result != float('inf') else -1

And here is the BFS solution in O(V+E)

from typing import List
from collections import defaultdict, deque

class Solution:
    def slidingPuzzle(self, board: List[List[int]]) -> int:
        def swap(s: str, zero_idx: int, new_idx: int) -> str:
            char_arr = list(s)
            char_arr[zero_idx], char_arr[new_idx] = char_arr[new_idx], char_arr[zero_idx]
            return "".join(char_arr)

        target = "123450"
        start = "".join(str(board[r][c]) for r in range(len(board)) for c in range(len(board[0])))

        visited = set()
        directions = [
            [1, 3],    # Neighbors for index 0
            [0, 2, 4], # Neighbors for index 1
            [1, 5],    # Neighbors for index 2
            [0, 4],    # Neighbors for index 3
            [1, 3, 5], # Neighbors for index 4
            [2, 4]     # Neighbors for index 5
        ]

        queue = deque([start])
        visited.add(start)
        res = 0

        while queue:
            size = len(queue)
            for _ in range(size):
                curr = queue.popleft()
                if curr == target:
                    return res

                zero_idx = curr.index('0')  # Find the index of '0'

                for dir_idx in directions[zero_idx]:  # Valid moves for the zero's position
                    swapped = swap(curr, zero_idx, dir_idx)
                    if swapped in visited:
                        continue
                    visited.add(swapped)
                    queue.append(swapped)
            res += 1

        return -1

Solution

  • I would start with a global variable dfs_calls = 0 and every time dsf() was called I would increment it.

    Well, I would not technically do that, but it is the easiest thing to do to augment your code without changing signatures and whatnot

    Then I would use a print inside your for loop that gave this value and the current depth for context.

    dfs_calls = 0
    def dfs(state: str, depth: int) -> int:
        global dfs_calls
        dfs_calls += 1
    
        if state == target:
            return depth
        
        if state in memo:
            return float('inf')  # State already visited
        
        memo.add(state)
        zero_idx = state.index('0')
        min_moves = float('inf')
    
        print(f"{dfs_calls} current_depth: { '.' * (depth // 10) }{ ' ' * 10 }", end="\r", flush=True)
        for neighbor in directions[zero_idx]:
            new_state = swap(state, zero_idx, neighbor)
            moves = dfs(new_state, depth + 1)
            min_moves = min(min_moves, moves)
        
        memo.remove(state)  # Backtrack
        return min_moves
    

    Note that print() is very slow and will dramtically impact the performance here.