Search code examples
pythonalgorithmtypeerrora-starpuzzle

A* algorithm TypeError: cannot unpack non-iterable int object


This is the python code which uses A* algorithm for finding solution for 8 puzzle problems, I got some error messages, how can I fix it?(The error message is under the code)

There are several object-oriented programming concepts for Problems class, Node class that are implemented to express the problem solution search that you need to understand in order to make the Python program complete. The priority queue is to make the nodes to be explored to be sorted according to their f-evaluation function score and return the min one as the first node to be searched next.

There is also a memorize function to memorize the heuristic value of state as a look-up table so that you don’t need to calculate the redundant computing of heuristic estimation value, so you can ignore it at this point if you don’t understand.

The components you need to implement is to make the abstract part of the program realizable for 8 -puzzle with the successor methods attached to a problem class which consists of initial state and goal state. Make sure the program can run correctly to generate the solution sequence that move the empty tile so that the 8-puzzle can move "Up", "Down", "Left", "Right", from initial state to goal state.

import math
infinity = math.inf
from itertools import chain
import numpy as np
import bisect

class memoize:
    def __init__(self, f, memo={}):
        self.f = f
        self.memo = {}
    def __call__(self, *args):
        if not str(args) in self.memo:
            self.memo[str(args)] = self.f(*args)
        return self.memo[str(args)]

def coordinate(state):
    index_state = {}
    index = [[0,0], [0,1], [0,2], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]]
    for i in range(len(state)):
        index_state[state[i]] = index[i]
    return index_state

def getInvCount(arr):
    inv_count = 0
    empty_value = -1
    for i in range(0, 9):
        for j in range(i + 1, 9):
            if arr[j] != empty_value and arr[i] != empty_value and arr[i] > arr[j]:
                inv_count += 1
    return inv_count

def isSolvable(puzzle) :
     inv_count = getInvCount([j for sub in puzzle for j in sub])
     return (inv_count % 2 == 0)
     

def linear(state):
    return sum([1 if state[i] != goal[i] else 0 for i in range(9)])

@memoize
def manhattan(state):
    index_goal = coordinate(goal)
    index_state = coordinate(state)
    
    mhd = 0
    
    for i in range(9):
        for j in range(2):
            mhd = abs(index_goal[i][j] - index_state[i][j]) + mhd
    
    return mhd

@memoize
def sqrt_manhattan(state):
    index_goal = coordinate(goal)
    index_state = coordinate(state)

    mhd = 0
    
    for i in range(9):
        for j in range(2):
            mhd = (index_goal[i][j] - index_state[i][j])**2 + mhd
    
    return math.sqrt(mhd)

@memoize
def max_heuristic(state):
    score1 = manhattan(state)
    score2 = linear(state)
    return max(score1, score2)

class PriorityQueueElmt:
    def __init__(self,val,e):
        self.val = val
        self.e = e
    
    def __lt__(self,other):
        return self.val < other.val
    
    def value(self):
        return self.val
    
    def elem(self):
        return self.e

class Queue:
    def __init__(self):
        pass

    def extend(self, items):
        for item in items: self.append(item)

class PriorityQueue(Queue):
    def __init__(self, order=min, f=None):
        self.A=[]
        self.order=order
        self.f=f
    def append(self, item):
        queueElmt = PriorityQueueElmt(self.f(item),item)
        bisect.insort(self.A, queueElmt)
    def __len__(self):
        return len(self.A)
    def pop(self):
        if self.order == min:
            return self.A.pop(0).elem()
        else:
            return self.A.pop().elem()

# Heuristics for 8 Puzzle Problem
   
class Problem:
    def __init__(self, initial, goal=None):
        self.initial = initial; self.goal = goal

    def successor(self, state):
        reachable = []
        def get_key(val):
            for key, value in index_state.items():
                if val == value:
                    return key
            return -1
        def candidate(state, Position):
            state = state.copy()
            zero_index = state.index(0)
            swap_index = state.index(get_key(Position))
            state[zero_index], state[swap_index] = state[swap_index], state[zero_index]
        return state

        index_state = coordinate(state)
        zero_position = index_state[0]
        move_pair = {"left":[zero_position[0], zero_position[1] - 1],
                     "right":[zero_position[0], zero_position[1] + 1],
                     "up":[zero_position[0] - 1, zero_position[1]],
                     "down":[zero_position[0] + 1, zero_position[1]]
                    }
        for action, position in move_pair.items():
            #print(action, position)
            if get_key(position) != -1:
                reachable.append((action, candidate(state, position)))
          #print(reachable)
        
        return reachable

       
    def goal_test(self, state):
        return state == self.goal

    def path_cost(self, c, state1, action, state2):
        return c + 1

    def value(self):
        abstract

class Node:
    def __init__(self, state, parent=None, action=None, path_cost=0, depth =0):
        self.parent = parent
        if parent:
            self.depth = parent.depth + 1
        else:
            self.depth = 0
        self.path_cost = path_cost
        self.state = state
        if action:
            self.action = action
        else: self.action = "init"
            
    def __repr__(self):
        return "Node state:\n " + str(np.array(self.state).reshape(3,3)) +"\n -> action: " + self.action + "\n -> depth: " + str(self.depth)


    def path(self):
        x, result = self, [self]
        while x.parent:
            result.append(x.parent)
            x = x.parent
        return result

    def expand(self, problem):
        for (act,n) in problem.successor(self.state):
            if n not in [node.state for node in self.path()]:
                yield Node(n, self, act,
                    problem.path_cost(self.path_cost, self.state, act, n))

def graph_search(problem, fringe):
    closed = {}
    fringe.append(Node(problem.initial,depth=0))
    while fringe:
        node = fringe.pop()
        if problem.goal_test(node.state):
            return node
        if str(node.state) not in closed:
            closed[str(node.state)] = True
            fringe.extend(node.expand(problem))
    return None

def best_first_graph_search(problem, f):
    return graph_search(problem, PriorityQueue(min, f))

def astar_search(problem, h = None):
    h = h or problem.h
    def f(n):
        return max(getattr(n, 'f', -infinity), n.path_cost + h(n.state))
    return best_first_graph_search(problem, f)

def print_path(path, method):
    print("*" * 30)
    print("\nPath:  (%s distance)" % method)
    for i in range(len(path)-1, -1, -1):
        print("-" * 15)
        print(path[i])
    
goal = [1, 2, 3, 4, 5, 6, 7, 8, 0]

# Solving the puzzle 
puzzle = [7, 2, 4, 5, 0, 6, 8, 3, 1]

if(isSolvable(np.array(puzzle).reshape(3,3))):  # even true
    # checks whether the initialized configuration is solvable or not
    print("Solvable!")
    problem = Problem(puzzle,goal)
    
    path = astar_search(problem, manhattan).path()
    print_path(path, "manhattan")
    
    path = astar_search(problem, linear).path()
    print_path(path, "linear")
    
    path = astar_search(problem, sqrt_manhattan).path()
    print_path(path, "sqrt_manhattan")
    
    path = astar_search(problem, max_heuristic).path()
    print_path(path, "max_heuristic")
    
else :
    print("Not Solvable!")  # non-even false
TypeError                                 Traceback (most recent call last)
<ipython-input-124-2a60ddc8c009> in <module>
      9     problem = Problem(puzzle,goal)
     10 
---> 11     path = astar_search(problem, manhattan).path()
     12     print_path(path, "manhattan")
     13 

<ipython-input-123-caa97275712e> in astar_search(problem, h)
     18     def f(n):
     19         return max(getattr(n, 'f', -infinity), n.path_cost + h(n.state))
---> 20     return best_first_graph_search(problem, f)
     21 
     22 def print_path(path, method):

<ipython-input-123-caa97275712e> in best_first_graph_search(problem, f)
     12 
     13 def best_first_graph_search(problem, f):
---> 14     return graph_search(problem, PriorityQueue(min, f))
     15 
     16 def astar_search(problem, h = None):

<ipython-input-123-caa97275712e> in graph_search(problem, fringe)
      8         if str(node.state) not in closed:
      9             closed[str(node.state)] = True
---> 10             fringe.extend(node.expand(problem))
     11     return None
     12 

<ipython-input-121-e5a968bd54f0> in extend(self, items)
     18 
     19     def extend(self, items):
---> 20         for item in items: self.append(item)
     21 
     22 class PriorityQueue(Queue):

<ipython-input-122-db21613469b9> in expand(self, problem)
     69 
     70     def expand(self, problem):
---> 71         for (act,n) in problem.successor(self.state):
     72             if n not in [node.state for node in self.path()]:
     73                 yield Node(n, self, act,

TypeError: cannot unpack non-iterable int object

Solution

  • I got some error messages, how can I fix it?

    There is one error message, The pieces of codes you get in the error message are the stack trace, which might help you to know how the execution got at the final point where the error occurred. In this case that is not so important. The essence of the error is this:

    for (act,n) in problem.successor(self.state)

    TypeError: cannot unpack non-iterable int object

    So this means that the successor method returned an int instead of a list.

    Looking at the code for successor, I notice that it intends to return a list called reachable, but there is a return statement right in the middle of the code, leaving the largest part of that code unexecuted (so-called "dead code"):

        return state
    

    This statement makes no sense where it is positioned. It seems to be an indentation problem: that return belongs inside the function just above it, like this:

        def candidate(state, Position):
            state = state.copy()
            zero_index = state.index(0)
            swap_index = state.index(get_key(Position))
            state[zero_index], state[swap_index] = state[swap_index], state[zero_index]
            return state  # <-- indentation!