In [ ]:
    
%%HTML
<style>
.container { width:100% }
</style>
    
The module Set implements sets as 
AVL trees.
The API provided by Set offers the following functions and methods:
Set() creates an empty set.S.isEmpty() checks whether the set Sis empty.S.member(x) checks whether x is an element of the set S.S.insert(x) inserts x into the set S.
This does not return a new set but rather modifies the set S.S.delete(x) deletes x from the set S.
This does not return a new set but rather modifies the set S.S.pop() returns the smallest element of the set S.
Furthermore, this element is removed from S.
Since sets are implemented as ordered binary trees, the elements of a set need to be comparable, i.e. if x and y are inserted into a set, then the 
expression x < y must return a Boolean value and < has to define 
linear order.The module Set can be used to implement a priority queue that supports the removal of elements.
In [ ]:
    
import Set
    
The function search takes four arguments to solve a search problem:
start is the start state of the search problem,goalis the goal state, andnext_states is a function with signature $\texttt{next_states}:Q \rightarrow 2^Q$, where $Q$ is the set of states.
For every state $s \in Q$, $\texttt{next_states}(s)$ is the set of states that can be reached from $s$ in one step.heuristic is a function that takes two states as arguments.  It 
returns an estimate of the length of the shortest path between these
states.size is the maximum number states that A$^*$ search is allowed 
 to explore.If successful, search returns a path from start to goal that is a solution of the search problem
$$ \langle Q, \texttt{next_states}, \texttt{start}, \texttt{goal} \rangle. $$
The function search implements A$^*$-IDA$^*$ search.
The main idea of A$^*$-IDA$^*$ is to run an $\mathrm{A}^*$ search from the node 
start until memory is more or less exhausted.  Then, we start $\mathrm{IDA}^*$ from the node goal node and search towards the node 
start until we find any of the nodes discovered by the $\mathrm{A}^*$ search 
that had been started from the node start node.
In [ ]:
    
def search(start, goal, next_states, heuristic, size):
    Parent   = { start:start }
    Distance = { start: 0 }           
    estGoal  = heuristic(start, goal)
    Estimate = { start: estGoal }
    Frontier = Set.Set()
    Frontier.insert( (estGoal, start) )
    while len(Distance) < size and not Frontier.isEmpty():
        estimate, state = Frontier.pop()
        if state == goal:
            return path_to(state, Parent)
        stateDist = Distance[state]
        for ns in next_states(state):
            oldEstimate = Estimate.get(ns, None)
            newEstimate = stateDist + 1 + heuristic(ns, goal)
            if oldEstimate == None or newEstimate < oldEstimate:
                Distance[ns] = stateDist + 1
                Estimate[ns] = newEstimate
                Parent[ns]   = state
                Frontier.insert( (newEstimate, ns) )
                if oldEstimate != None:
                    Frontier.delete( (oldEstimate, ns) )
    Path = id_search(goal, start, next_states, heuristic, Distance)
    return path_to(Path[-1], Parent) + Path[::-1][1:]
    
In [ ]:
    
def id_search(goal, start, next_states, heuristic, Distance):
    limit = 0
    while True:
        print(f'limit = {limit}')
        Path = dl_search([goal], start, next_states, limit, heuristic, Distance)
        if isinstance(Path, list):
            return Path
        limit = Path
    
In [ ]:
    
def dl_search(Path, start, next_states, limit, heuristic, Distance):
    state = Path[-1]
    total = len(Path) - 1 + heuristic(state, start)
    if total > limit:
        return total
    if state in Distance:
        return Path
    smallest = float('Inf')
    for ns in next_states(state):
        if ns not in Path:
            result = dl_search( Path + [ns], start, next_states, 
                                limit, heuristic, Distance
                              )
            if isinstance(result, list):
                return result
            smallest = min(smallest, result)
    return smallest
    
Given a state and a parent dictionary Parent, the function path_to returns a path leading to the given state.
In [ ]:
    
def path_to(state, Parent):
    p = Parent[state]
    if p == state:
        return [state]
    return path_to(p, Parent) + [state]
    
Lets draw the start state and animate the solution that has been found.
In [ ]:
    
%run Sliding-Puzzle.ipynb
    
In [ ]:
    
%%time
Path = search(start, goal, next_states, manhattan, 3000)
print(len(Path)-1)
    
In [ ]:
    
animation(Path)
    
In [ ]:
    
%%time
Path = search(start2, goal2, next_states, manhattan, 3000)
print(len(Path)-1)
    
In [ ]:
    
animation(Path)
    
In [ ]: