8 Puzzle (python)

重排九宫

+---+---+---+      +---+---+---+
| 1 | 2 | 3 |      | 8 | 7 | 6 |
+---+---+---+      +---+---+---+
| 4 | 5 | 6 |  ==> | 5 | 4 | 3 |
+---+---+---+      +---+---+---+
| 7 | 8 |   |      | 2 | 1 |   |
+---+---+---+      +---+---+---+

$ 8puzzle.py -f2
# 30
Number of states enqueued = 14754
#!/usr/bin/env python
 
import sys
from optparse import OptionParser
import math
from struct import pack
import heapq
 
class Solver:
    def __init__(self, n):
        self.N = n
        self.L = n * n
 
        self.GOAL = range(1, self.L)
        self.GOAL.append(0)
 
        # slide rules
        self.SR = {}
        for i in range(self.L):
            s = []
            if i - self.N >= 0:
                s.append(i - self.N)
            if (i % self.N) - 1 >= 0:
                s.append(i - 1)
            if (i % self.N) + 1 < self.N:
                s.append(i + 1)
            if i + self.N < self.L:
                s.append(i + self.N)
            self.SR[i] = s
 
        # queue
        self.queue = []
        self.enqueued = {}
 
        # verbose
        #self.verbose = 104999
        self.verbose = 8963
 
        # h
        self.w = 1
        self.h = self.heuristics
 
    def is_solvable(self, tiles):
        x = 0
        for p in range(len(tiles)):
            a = tiles[p]
            if a < 2 :
                continue
            for b in tiles[p:]:
                if b == 0:
                    continue
                if a > b:
                    x = x + 1
        return (x & 1) == 0
 
    def neighbors(self, tiles):
        n = []
        a = tiles.index(0)
        for b in self.SR[a]:
            n.append(self.swap(list(tiles), a, b))
        return n
 
    def swap(self, tiles, a, b):
        tiles[a], tiles[b] = tiles[b], tiles[a]
        return tiles
 
    def display(self, tiles):
        for i in range(self.L):
            if tiles[i]:
                print '%(n)#2d' % {'n': tiles[i]},
            else:
                print '  ',
            if i % self.N == self.N - 1:
                print
 
    def enqueue(self, state):
        (tiles, parent, h, g) = state
 
        if self.verbose > 0 and len(self.enqueued) % self.verbose == self.verbose - 1:
            print " -->", len(self.enqueued), g
 
        f = h * self.w + g;
        heapq.heappush(self.queue, (f, state))
 
    def dequeue(self):
        if len(self.queue) <= 0:
            return None
        (f, state) = heapq.heappop(self.queue)
        return state
 
    def heuristics(self, tiles):
        return 0;
 
    def manhattan(self, tiles):
        h = 0
        for i in range(self.L):
            n = tiles[i]
            if n == 0:
                continue
            h += int(abs(n - 1 - i) / self.N) + (abs(n - 1 - i) % self.N)
        return h
 
    def hamming(self, tiles):
        h = 0
        for i in range(self.L):
            n = tiles[i]
            if n > 0 and n - 1 != i:
                h += 1
        return h
 
    def solve(self, initial):
        if not self.is_solvable(initial):
            return None
 
        state = (initial, None, self.h(initial), 0);
        if initial == self.GOAL:
            return state
 
        self.enqueue(state)
 
        while True:
            state = self.dequeue()
            if (not state):
                break
 
            (tiles, parent, h, g) = state
            neighbors = self.neighbors(tiles)
            for n_tiles in neighbors:
                if n_tiles == self.GOAL:
                    return (n_tiles, state, 0, g + 1)
 
                packed = pack(self.L*'B', *n_tiles)
                if (packed in self.enqueued):
                    continue;
                self.enqueued[packed] = True                       
 
                n_state = (n_tiles, state, self.h(n_tiles), g + 1)
                self.enqueue(n_state)
 
def main(options, args):
    initial = []
    if args:
        for n in args:
            initial.append(int(n))
    else:
        initial = [8,7,6,5,4,3,2,1,0]
 
    solver = Solver(int(math.sqrt(len(initial))))
 
    solver.verbose = int(options.verbose)
    solver.w = float(options.weight)
    if int(options.function) == 1:
        solver.h = solver.hamming
    elif int(options.function) == 2:
        solver.h = solver.manhattan
 
    state = solver.solve(initial)
    if not state:
        print "No solution possible"
        return 1
 
    solution = []
    while state:
        (tiles, parent, h, g) = state
        solution.insert(0, tiles)
        state = parent
 
    n = 0
    for tiles in solution:
        print "#", n
        solver.display(tiles)
        print
        n += 1
 
    print "Number of states enqueued =", len(solver.enqueued)
    return 0
 
if __name__ == '__main__':
    parser = OptionParser(usage="usage: %prog [options] [tile] [tile] [tiles ...]")
    parser.add_option("-v", "--verbose", metavar="<level>",
            default=8963)
    parser.add_option("-f", "--function", metavar="<fid>",
            help="heuristics function. 1 for hamming, 2 for manhattan [default: None as breadth first]",
            default=0)
    parser.add_option("-w", "--weight", metavar="<n>",
            help="weighting of the heuristics function [default: 1]",
            default=1)
    (options, args) = parser.parse_args()
 
    sys.exit(main(options, args))

15 puzzle

+----+----+----+----+       +----+----+----+----+
| 15 | 14 | 13 | 12 |       |  1 |  2 |  3 |  4 |
+----+----+----+----+       +----+----+----+----+
| 11 | 10 |  9 |  8 |       |  5 |  6 |  7 |  8 |
+----+----+----+----+  ==>  +----+----+----+----+
|  7 |  6 |  5 |  4 |       |  9 | 10 | 11 | 12 |
+----+----+----+----+       +----+----+----+----+
|  3 |  1 |  2 |    |       | 13 | 15 | 14 |    |
+----+----+----+----+       +----+----+----+----+

$ 8puzzle.py -f2 -w1.9 15 14 13 12 11 10 9 8 7 6 5 4 3 1 2 0
# 76
Number of states enqueued = 2812200