Newer
Older
each other. A state is represented as an N-element array, where
a value of r in the c-th entry means there is a queen at column c,
row r, and a value of None means that the c-th column has not been
filled in yet. We fill in columns left to right.
>>> depth_first_tree_search(NQueensProblem(8))
<Node [7, 3, 0, 2, 5, 1, 6, 4]>
"""
def __init__(self, N):
self.N = N
self.initial = [None] * N
"""In the leftmost empty column, try all non-conflicting rows."""
if state[-1] is not None:
else:
col = state.index(None)
if not self.conflicted(state, row, col)]
"""Place the next queen at the given row."""
col = state.index(None)
new = state[:]
new[col] = row
return new
def conflicted(self, state, row, col):
"""Would placing a queen at (row, col) conflict with anything?"""
withal
a validé
return any(self.conflict(row, col, state[c], c)
for c in range(col))
def conflict(self, row1, col1, row2, col2):
"""Would putting two queens in (row1, col1) and (row2, col2) conflict?"""
return (row1 == row2 or # same row
col1 == col2 or # same column
row1 - col1 == row2 - col2 or # same \ diagonal
row1 + col1 == row2 + col2) # same / diagonal
def goal_test(self, state):
"""Check if all columns filled, no conflicts."""
return False
withal
a validé
return not any(self.conflicted(state, state[col], col)
for col in range(len(state)))
# ______________________________________________________________________________
# Inverse Boggle: Search for a high-scoring Boggle board. A good domain for
# iterative-repair and related search techniques, as suggested by Justin Boyan.
ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
cubes16 = ['FORIXB', 'MOQABJ', 'GURILW', 'SETUPL',
'CMPDAE', 'ACITAO', 'SLCRAE', 'ROMASH',
'NODESW', 'HEFIYE', 'ONUDTK', 'TEVIGN',
'ANEDVZ', 'PINESH', 'ABILYT', 'GKYLEU']
def random_boggle(n=4):
"""Return a random Boggle board of size n x n.
We represent a board as a linear list of letters."""
cubes = [cubes16[i % 16] for i in range(n * n)]
random.shuffle(cubes)
# The best 5x5 board found by Boyan, with our word list this board scores
# 2274 words, for a score of 9837
boyan_best = list('RSTCSDEIAEGNLRPEATESMSSID')
def print_boggle(board):
for i in range(n2):
if i % n == 0 and i > 0:
print()
if board[i] == 'Q':
print('Qu', end=' ')
else:
print(str(board[i]) + ' ', end=' ')
"""Return a list of lists, where the i-th element is the list of indexes
for the neighbors of square i."""
if cache.get(n2):
return cache.get(n2)
n = exact_sqrt(n2)
neighbors = [None] * n2
for i in range(n2):
neighbors[i] = []
on_top = i < n
on_bottom = i >= n2 - n
on_left = i % n == 0
on_right = (i+1) % n == 0
if not on_top:
neighbors[i].append(i - n)
if not on_left:
neighbors[i].append(i - n - 1)
if not on_right:
neighbors[i].append(i - n + 1)
if not on_bottom:
if not on_left:
neighbors[i].append(i + n - 1)
if not on_right:
neighbors[i].append(i + n + 1)
if not on_left:
neighbors[i].append(i - 1)
if not on_right:
neighbors[i].append(i + 1)
cache[n2] = neighbors
return neighbors
def exact_sqrt(n2):
"""If n2 is a perfect square, return its square root, else raise error."""
n = int(math.sqrt(n2))
assert n * n == n2
return n
# _____________________________________________________________________________
class Wordlist:
"""This class holds a list of words. You can use (word in wordlist)
to check if a word is in the list, or wordlist.lookup(prefix)
to see if prefix starts any of the words in the list."""
def __init__(self, file, min_len=3):
lines = file.read().upper().split()
self.words = [word for word in lines if len(word) >= min_len]
self.words.sort()
self.bounds = {}
for c in ALPHABET:
c2 = chr(ord(c) + 1)
self.bounds[c] = (bisect.bisect(self.words, c),
bisect.bisect(self.words, c2))
def lookup(self, prefix, lo=0, hi=None):
"""See if prefix is in dictionary, as a full word or as a prefix.
Return two values: the first is the lowest i such that
words[i].startswith(prefix), or is None; the second is
True iff prefix itself is in the Wordlist."""
words = self.words
i = bisect.bisect_left(words, prefix, lo, hi)
if i < len(words) and words[i].startswith(prefix):
return i, (words[i] == prefix)
return None, False
return self.lookup(word)[1]
return len(self.words)
# _____________________________________________________________________________
class BoggleFinder:
"""A class that allows you to find all the words in a Boggle board."""
wordlist = None # A class variable, holding a wordlist
def __init__(self, board=None):
if BoggleFinder.wordlist is None:
BoggleFinder.wordlist = Wordlist(open_data("EN-text/wordlist.txt"))
self.found = {}
if board:
self.set_board(board)
def set_board(self, board=None):
"""Set the board, and find all the words in it."""
if board is None:
board = random_boggle()
self.board = board
self.neighbors = boggle_neighbors(len(board))
self.found = {}
for i in range(len(board)):
lo, hi = self.wordlist.bounds[board[i]]
self.find(lo, hi, i, [], '')
return self
def find(self, lo, hi, i, visited, prefix):
"""Looking in square i, find the words that continue the prefix,
considering the entries in self.wordlist.words[lo:hi], and not
revisiting the squares in visited."""
return
wordpos, is_word = self.wordlist.lookup(prefix, lo, hi)
if wordpos is not None:
self.found[prefix] = True
visited.append(i)
c = self.board[i]
prefix += c
self.find(wordpos, hi, j, visited, prefix)
visited.pop()
scores = [0, 0, 0, 0, 1, 2, 3, 5] + [11] * 100
def score(self):
"""The total score for the words found, according to the rules."""
return sum([self.scores[len(w)] for w in self.words()])
def __len__(self):
return len(self.found)
# _____________________________________________________________________________
def boggle_hill_climbing(board=None, ntimes=100, verbose=True):
"""Solve inverse Boggle by hill-climbing: find a high-scoring board by
starting with a random one and changing it."""
finder = BoggleFinder()
if board is None:
board = random_boggle()
best = len(finder.set_board(board))
for _ in range(ntimes):
i, oldc = mutate_boggle(board)
new = len(finder.set_board(board))
if new > best:
best = new
print_boggle(board)
return board, best
def mutate_boggle(board):
i = random.randrange(len(board))
oldc = board[i]
# random.choice(boyan_best)
board[i] = random.choice(random.choice(cubes16))
return i, oldc
# ______________________________________________________________________________
# Code to compare searchers on various problems.
class InstrumentedProblem(Problem):
"""Delegates to a problem, and keeps statistics."""
self.problem = problem
self.succs = self.goal_tests = self.states = 0
self.found = None
def actions(self, state):
self.succs += 1
return self.problem.actions(state)
def result(self, state, action):
self.states += 1
return self.problem.result(state, action)
def goal_test(self, state):
self.goal_tests += 1
result = self.problem.goal_test(state)
self.found = state
return result
def path_cost(self, c, state1, action, state2):
return self.problem.path_cost(c, state1, action, state2)
def value(self, state):
return self.problem.value(state)
def __getattr__(self, attr):
def __repr__(self):
return '<{:4d}/{:4d}/{:4d}/{}>'.format(self.succs, self.goal_tests,
def compare_searchers(problems, header,
searchers=[breadth_first_tree_search,
breadth_first_search,
depth_first_graph_search,
iterative_deepening_search,
depth_limited_search,
recursive_best_first_search]):
def do(searcher, problem):
p = InstrumentedProblem(problem)
searcher(p)
return p
table = [[name(s)] + [do(s, p) for p in problems] for s in searchers]
print_table(table, header)
def compare_graph_searchers():
compare_searchers(problems=[GraphProblem('Arad', 'Bucharest', romania_map),
GraphProblem('Oradea', 'Neamt', romania_map),
GraphProblem('Q', 'WA', australia_map)],
header=['Searcher', 'romania_map(Arad, Bucharest)',
'romania_map(Oradea, Neamt)', 'australia_map'])