Advertisement
Guest User

dancing links

a guest
Jul 10th, 2012
183
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.85 KB | None | 0 0
  1. # Solve the exact cover problem. Given a board consisting of a set of "squares", and a set of
  2. # "pieces", each of which covers a given subset of the "squares", choose a subset of the pieces
  3. # such that each square is covered by exactly one piece in the subset.
  4.  
  5. # The problem is represented by a matrix where the squares are columns and the pieces are rows.
  6.  
  7. class Problem:  # The main header node of the matrix
  8.     def __init__(self, snames0, cons):
  9.         # snames0 is a list of all square names
  10.         # Cons is a dict mapping each piece to the names of the squares it covers
  11.         self.squares = dict((sname, Header(sname)) for sname in snames0)  # all column headers
  12.         joinrow([self] + self.squares.values())  # connect the control row
  13.         for pname, snames in cons.items():  # add one row for each piece
  14.             nodes = [Node(pname, self.squares[sname]) for sname in snames]
  15.             joinrow(nodes)
  16.  
  17.     def headers(self):  # all currently active headers
  18.         h = self.right
  19.         while h is not self:
  20.             yield h
  21.             h = h.right
  22.    
  23.     def solve(self):
  24.         self.solvelist = []
  25.         self.solvestep()
  26.         return self.solvelist  # will be empty if no solution was found
  27.        
  28.     def solvestep(self):  # recursive function to get one piece of the solution
  29.         if self.right is self: return True  # All squares have been claimed, solution is found
  30.         # choose a square with the fewest number of pieces that can cover it
  31.         square = min(self.headers(), key = lambda h: h.count)
  32.         if square.count == 0: return False  # No way to cover this square; solution is impossible
  33.         for piece in square.pieces():  # Try each of the pieces that can cover this square
  34.             self.solvelist.append(piece.name)  # push this piece onto the solve list
  35.             piece.place()  # place this piece onto the grid and claim the corresponding squares
  36.             if self.solvestep(): # try to solve the reduced problem
  37.                 return True   # A solution has been found
  38.             del self.solvelist[-1]  # solution failed; pop this piece off the solve list
  39.             piece.unplace()  # free up the claimed squares
  40.         return False  # No solution was found
  41.  
  42. class Header:  # A column header, corresponding to a square
  43.     def __init__(self, squarename):
  44.         self.name = squarename
  45.         self.down = self   # start it off in a column by itself
  46.         self.up = self
  47.         self.count = 0   # number of nodes in this column (ie, pieces that can cover this square)
  48.  
  49.     def pieces(self, rev=False):  # all current nodes in this column
  50.         p = self.up if rev else self.down
  51.         while p is not self:
  52.             yield p
  53.             p = p.up if rev else p.down
  54.    
  55.     def claim(self):  # call this when a piece claims this square
  56.         for piece in self.pieces(): # remove all pieces that could occupy this square
  57.             piece.remove_row()
  58.         self.right.left = self.left  # remove this column from matrix
  59.         self.left.right = self.right
  60.    
  61.     def unclaim(self):  # undo a call to claim
  62.         self.right.left = self  # restore this column into matrix
  63.         self.left.right = self
  64.         for piece in self.pieces(rev=True):  # restore pieces that could occupy this square
  65.             piece.restore_row()
  66.  
  67. class Node:  # A node in the matrix, corresponding to a piece + square
  68.     def __init__(self, piecename, head):
  69.         self.name = piecename
  70.         self.head = head  # the column header for the column this node is in
  71.         self.up = head.up  # add to the bottom of the given column
  72.         self.down = head
  73.         self.restore_vert()
  74.        
  75.     def squares(self, rev=False):  # all squares this piece covers
  76.         s = self.left if rev else self.right
  77.         while s is not self:
  78.             yield s
  79.             s = s.left if rev else s.right
  80.    
  81.     def place(self):  # place this piece onto the board and claim the corresponding squares
  82.         self.remove_row()
  83.         for square in self.squares():
  84.             square.head.claim()
  85.         self.head.claim()
  86.    
  87.     def unplace(self):  # remove this piece from the board (and put it back into the matrix)
  88.         self.head.unclaim()
  89.         for square in self.squares(rev=True):
  90.             square.head.unclaim()
  91.         self.restore_row()
  92.  
  93.     def remove_vert(self):   # Disconnect this node from the matrix vertically
  94.         self.up.down = self.down
  95.         self.down.up = self.up
  96.         self.head.count -= 1    
  97.     def restore_vert(self):  # Connect this node to the matrix vertically
  98.         self.up.down = self
  99.         self.down.up = self
  100.         self.head.count += 1
  101.    
  102.     def remove_row(self):   # Vertically disconnect all other nodes in this row
  103.         for square in self.squares():
  104.             square.remove_vert()
  105.     def restore_row(self):  # Restore vertical connections of other nodes in this row
  106.         for square in self.squares():
  107.             square.restore_vert()
  108.  
  109. def joinrow(nodes):
  110.     # Join a list of nodes together horizontally
  111.     for j in range(len(nodes)):
  112.         nodes[j].right = nodes[(j+1)%len(nodes)]
  113.         nodes[j].left = nodes[(j-1)%len(nodes)]
  114.  
  115. if __name__ == "__main__":
  116.     import random
  117.     # solve the exact cover problem of placing trominoes onto a board with some missing squares
  118.     n, m = 15, 15  # nxn board with m missing squares
  119.     trominoes = [((0,0),(1,0),(2,0)), ((0,0),(0,1),(0,2)), ((0,0),(1,0),(0,1)),
  120.                  ((0,0),(1,0),(1,1)), ((0,0),(0,1),(1,1)), ((1,0),(0,1),(1,1))]
  121.  
  122.     squares = [(x, y) for x in range(n) for y in range(n)]
  123.     grid = dict((square, ".") for square in squares)  # text representation of board
  124.     for _ in range(m):  # Randomly remove m squares from the board
  125.         square = random.choice(squares)
  126.         squares.remove(square)
  127.         grid[square] = "*"
  128.     def printgrid():
  129.         print "\n".join(" ".join(grid[(x,y)] for x in range(n)) for y in range(n))
  130.         print
  131.     printgrid()
  132.  
  133.     # Find every possible place you could place a tromino (including some that go off the board)
  134.     pieces = []
  135.     for x0 in range(n):
  136.         for y0 in range(n):
  137.             for tri in trominoes:
  138.                 pieces.append(tuple((x0+dx,y0+dy) for dx,dy in tri))
  139.     # Remove any pieces that don't fit
  140.     pieces = [piece for piece in pieces if all(square in squares for square in piece)]
  141.     # A map from each piece to the squares it covers (in this case they're the same thing)
  142.     cons = dict((piece, piece) for piece in pieces)
  143.  
  144.     # solve the problem
  145.     problem = Problem(squares, cons)
  146.     solution = problem.solve()
  147.    
  148.     # print the solution
  149.     if solution:
  150.         for c, piece in enumerate(sorted(solution)):
  151.             for square in piece:
  152.                 grid[square] = chr(ord("A") + c % 26)
  153.  
  154.         printgrid()
  155.     else:
  156.         print "No solution found"
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement