search.py 45,5 ko
Newer Older
Chipe1's avatar
Chipe1 a validé

# ______________________________________________________________________________
class NQueensProblem(Problem):
    """The problem of placing N queens on an NxN board with none attacking
withal's avatar
withal a validé
    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

    def actions(self, state):
        """In the leftmost empty column, try all non-conflicting rows."""
MircoT's avatar
MircoT a validé
            return []  # All columns filled; no successors
            return [row for row in range(self.N)
                    if not self.conflicted(state, row, col)]

    def result(self, state, row):
        """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?"""
        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
        """Check if all columns filled, no conflicts."""
withal's avatar
withal a validé
        if state[-1] is None:
        return not any(self.conflicted(state, state[col], col)
                       for col in range(len(state)))
# ______________________________________________________________________________
withal's avatar
withal a validé
# 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)]
MircoT's avatar
MircoT a validé
    return list(map(random.choice, cubes))
withal's avatar
withal a validé
# 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')

    """Print the board in a 2-d array."""
MircoT's avatar
MircoT a validé
    n2 = len(board)
    n = exact_sqrt(n2)
MircoT's avatar
MircoT a validé
        if i % n == 0 and i > 0:
            print()
        if board[i] == 'Q':
            print('Qu', end=' ')
        else:
            print(str(board[i]) + ' ', end=' ')
MircoT's avatar
MircoT a validé
    print()
C.G.Vedant's avatar
C.G.Vedant a validé
def boggle_neighbors(n2, cache={}):
    """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)
MircoT's avatar
MircoT a validé
            if not on_left:
                neighbors[i].append(i - n - 1)
            if not on_right:
                neighbors[i].append(i - n + 1)
withal's avatar
withal a validé
            neighbors[i].append(i + n)
MircoT's avatar
MircoT a validé
            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)
    """If n2 is a perfect square, return its square root, else raise error."""
    n = int(math.sqrt(n2))
    assert n * n == n2
    return n

# _____________________________________________________________________________
    """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."""
Surya Teja Cheedella's avatar
Surya Teja Cheedella a validé
    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
MircoT's avatar
MircoT a validé
        if hi is None:
            hi = len(words)
        i = bisect.bisect_left(words, prefix, lo, hi)
withal's avatar
withal a validé
        if i < len(words) and words[i].startswith(prefix):
            return i, (words[i] == prefix)
withal's avatar
withal a validé
        else:
withal's avatar
withal a validé
    def __contains__(self, word):
        return self.lookup(word)[1]
withal's avatar
withal a validé
    def __len__(self):
# _____________________________________________________________________________
    """A class that allows you to find all the words in a Boggle board."""
MircoT's avatar
MircoT a validé
    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):
Kaivalya Rawal's avatar
Kaivalya Rawal a validé
        """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."""
withal's avatar
withal a validé
        if i in visited:
            return
        wordpos, is_word = self.wordlist.lookup(prefix, lo, hi)
        if wordpos is not None:
withal's avatar
withal a validé
            if is_word:
                self.found[prefix] = True
            visited.append(i)
            c = self.board[i]
MircoT's avatar
MircoT a validé
            if c == 'Q':
                c = 'QU'
withal's avatar
withal a validé
            for j in self.neighbors[i]:
                self.find(wordpos, hi, j, visited, prefix)
            visited.pop()

withal's avatar
withal a validé
    def words(self):
Kaivalya Rawal's avatar
Kaivalya Rawal a validé
        """The words found."""
MircoT's avatar
MircoT a validé
        return list(self.found.keys())

    scores = [0, 0, 0, 0, 1, 2, 3, 5] + [11] * 100

    def score(self):
Kaivalya Rawal's avatar
Kaivalya Rawal a validé
        """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):
Kaivalya Rawal's avatar
Kaivalya Rawal a validé
        """The number of words found."""
# _____________________________________________________________________________
peter.norvig's avatar
peter.norvig a validé
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
MircoT's avatar
MircoT a validé
            if verbose:
                print(best, _, board)
MircoT's avatar
MircoT a validé
            board[i] = oldc  # Change back
peter.norvig's avatar
peter.norvig a validé
    if verbose:
def mutate_boggle(board):
    i = random.randrange(len(board))
    oldc = board[i]
MircoT's avatar
MircoT a validé
    # random.choice(boyan_best)
    board[i] = random.choice(random.choice(cubes16))
# ______________________________________________________________________________
withal's avatar
withal a validé
# Code to compare searchers on various problems.
class InstrumentedProblem(Problem):
    """Delegates to a problem, and keeps statistics."""

withal's avatar
withal a validé
    def __init__(self, problem):
        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)
withal's avatar
withal a validé
        if 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)

withal's avatar
withal a validé
        return getattr(self.problem, attr)
        return '<{:4d}/{:4d}/{:4d}/{}>'.format(self.succs, self.goal_tests,
Kaivalya Rawal's avatar
Kaivalya Rawal a validé
                                               self.states, str(self.found)[:4])
def compare_searchers(problems, header,
                      searchers=[breadth_first_tree_search,
                                 breadth_first_search,
                                 depth_first_graph_search,
                                 iterative_deepening_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():
Peter Norvig's avatar
Peter Norvig a validé
    """Prints a table of search results."""
Surya Teja Cheedella's avatar
Surya Teja Cheedella a validé
    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'])