Решатель 8 плиток с повторяющимися узлами — Python

Я пытаюсь решить головоломку с 8 плитками, используя такие методы, как поиск BFS, DFS, Greedy и A *, используя манхэттенское расстояние в качестве моего эвристического решения.

Проблема в том, что, хотя я могу решить большое количество задач, проблема заключается в некоторых головоломках, может случиться так, что дочерние элементы, которые я получаю при расширении родительского узла, уже могут быть в более старых узлах.

Я не знаю, смог ли я хорошо объясниться, но моя главная проблема заключается во всем, что я пытался увидеть, не находятся ли новые узлы, которые я создаю, уже на старых узлах.

С этой проблемой я обычно добираюсь до глубины 9, а затем моя программа не продвигается вперед или не дает решения.

Одна из моих идей заключалась в использовании кода:

if node in prev:
    continue
prev.append(node)

Но я думаю, что я иду неправильным путем.

Я делаю это на Python, и вот мой код, если кто-то может мне помочь.

#!/usr/bin/python

import sys
import copy


class Board:
    def __init__(self, matrix, whitepos=None):
        self.matrix = matrix
        self.whitepos = whitepos
        if not whitepos:
            for y in xrange(3):
                for x in xrange(3):
                    if board[y][x] == 0:
                        self.whitepos = (x, y)


def is_final_state(board):
    final = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
    for y in xrange(3):
        for x in xrange(3):
            if board.matrix[y][x] != final[y][x]:
                return False
    return True


def get_whitepos(board):
    return board.whitepos


def move(board, x, y, dx, dy):
    b = copy.deepcopy(board.matrix)
    b[y][x] = b[y + dy][x + dx]
    b[y + dy][x + dx] = 0
    return Board(b, (x + dx, y + dy))


def manhattan_heur(board):
    finalpos = [(1, 1), (0, 0), (1, 0), (2, 0), (2, 1), (2, 2), (1, 2), (0, 2),
                (0, 1)]
    cost = 0
    for y in xrange(3):
        for x in xrange(3):
            t = board.matrix[y][x]
            xf, yf = finalpos[t]
            cost += abs(xf - x) + abs(yf - y)
    return cost


def wrongplace_heur(board):
    finalpos = [(1, 1), (0, 0), (1, 0), (2, 0), (2, 1), (2, 2), (1, 2), (0, 2),
                (0, 1)]
    cost = 0
    for y in xrange(3):
        for x in xrange(3):
            t = board.matrix[y][x]
            if finalpos[t] != (x, y):
                cost += 1
    return cost


def heuristic(board):
    return manhattan_heur(board)


class Node:
    def __init__(self, board, parent):
        self.state = board
        self.parent = parent
        if not parent:
            self.g = 0
        else:
            self.g = parent.g + 1
        self.h = heuristic(board)

    def test_goal(self):
        return is_final_state(self.state)

    def expand(self):
        children = []
        b = self.state
        x, y = get_whitepos(b)
        if x > 0:
            children.append(Node(move(b, x, y, -1, 0), self))
        if x < 2:
            children.append(Node(move(b, x, y, +1, 0), self))
        if y > 0:
            children.append(Node(move(b, x, y, 0, -1), self))
        if y < 2:
            children.append(Node(move(b, x, y, 0, +1), self))
        return children


class Solution:
    def __init__(self, node, mem_needed, steps):
        self.node = node
        self.mem_needed = mem_needed
        self.iterations = steps

    def inc(self, other):
        self.node = other.node
        self.mem_needed = max(self.mem_needed, other.mem_needed)
        self.iterations += other.iterations


def search(board, queue_fn, queue_arg=None):
    max_nodes = 1
    steps = 0
    nodes = [Node(Board(board), None)]
    prev = []
    depth = 0
    while nodes:
        node = nodes.pop(0)

        if node.g > depth:
            depth = node.g
            print depth

        if node in prev:
            continue
        prev.append(node)

        if node.test_goal():
            return Solution(node, max_nodes, steps)
        new_nodes = node.expand()
        nodes = queue_fn(nodes, new_nodes, queue_arg)

        max_nodes = max(max_nodes, len(nodes))
        steps += 1
    return Solution(None, max_nodes, steps)


def fifo_queue(nodes, new_nodes, _):
    nodes.extend(new_nodes)
    return nodes


def bl_search(board):
    return search(board, fifo_queue)


def lifo_queue(nodes, new_nodes, _):
    new_nodes.extend(nodes)
    return new_nodes


def dfs_search(board):
    return search(board, lifo_queue)


def bpl_queue(nodes, new_nodes, max_depth):
    def f(n):
        return n.g <= max_depth

    new_nodes = filter(f, new_nodes)
    new_nodes.extend(nodes)
    return new_nodes


def bpi_search(board):
    solution = Solution(None, 0, 0)
    for max_depth in xrange(0, sys.maxint):
        sol = search(board, bpl_queue, max_depth)
        solution.inc(sol)
        if solution.node:
            return solution


def sort_queue(nodes, new_nodes, cmp):
    nodes.extend(new_nodes)
    nodes.sort(cmp)
    return nodes


def guloso2_search(board):
    def cmp(n1, n2):
        return n1.h - n2.h

    return search(board, sort_queue, cmp)


def astar_search(board):
    def cmp(n1, n2):
        return (n1.g + n1.h) - (n2.g + n2.h)

    return search(board, sort_queue, cmp)


def print_solution(search, sol):
    print
    print "*", search
    node = sol.node
    if node:
        print "moves:", node.g
        while node:
            print "\t", node.state.matrix
            node = node.parent
    else:
        print "no solution found"
    print "nodes needed:", sol.mem_needed
    print "iterations:  ", sol.iterations


board = [[6, 5, 7], [2, 0, 1], [8, 4, 3]]

print_solution("bl", bl_search(board))
print_solution("dfs", dfs_search(board))
print_solution("bpi", bpi_search(board))
print_solution("guloso2", guloso2_search(board))
print_solution("astar", astar_search(board))

person Rasec Noir    schedule 06.03.2013    source источник
comment
Восемь пробелов несколько неуместны для программирования на Python. Людям будет легче читать (и с большей вероятностью они ответят), если вы перейдете на четыре пробела.   -  person Waleed Khan    schedule 06.03.2013
comment
у меня возникли проблемы с публикацией кода, так как я впервые размещал код здесь, и поэтому он был с 8 пробелами   -  person Rasec Noir    schedule 06.03.2013
comment
Переформатировал его для вас и заменил некоторые странные if x == None: и if x != None: на if not x: и if x:.   -  person maksimov    schedule 06.03.2013
comment
Благодарю. я должен немного попрактиковаться в форматировании кода.   -  person Rasec Noir    schedule 06.03.2013


Ответы (1)


Похоже, вы все делаете правильно, но вам нужно определить методы __eq__ и __ne__ в классе Node; в противном случае node in prev всегда будет возвращать False, потому что Python не знает, как сравнивать node с элементами списка. Ознакомьтесь с документацией по модели данных Python, чтобы узнать, как работает сравнение. на пользовательских типах.

Я взял ваш код и добавил пару (очень наивных) методов для проверки на равенство, и он больше не зависает. Также стоит отметить, что ваши классы должны наследоваться от базы object (см. ниже). Вот изменения, которые я сделал (в контексте):

class Board(object):
    def __init__(self, matrix, whitepos=None):
        self.matrix = matrix
        self.whitepos = whitepos
        if not whitepos:
            for y in xrange(3):
                for x in xrange(3):
                    if board[y][x] == 0:
                        self.whitepos = (x, y)
    def __eq__(self, o):
        # Note that comparing whitepos is not strictly necessary; but I left 
        # it in as a safety measure in case the board state gets corrupted.
        # If speed becomes an issue, take it out.
        return (self.matrix, self.whitepos) == (o.matrix, o.whitepos)

class Node(object):
    def __init__(self, board, parent):
        self.state = board
        self.parent = parent
        if not parent:
            self.g = 0
        else:
            self.g = parent.g + 1
        self.h = heuristic(board)

    def test_goal(self):
        return is_final_state(self.state)

    def expand(self):
        children = []
        b = self.state
        x, y = get_whitepos(b)
        if x > 0:
            children.append(Node(move(b, x, y, -1, 0), self))
        if x < 2:
            children.append(Node(move(b, x, y, +1, 0), self))
        if y > 0:
            children.append(Node(move(b, x, y, 0, -1), self))
        if y < 2:
            children.append(Node(move(b, x, y, 0, +1), self))
        return children

    def __eq__(self, o):
        # Note that you don't have to compare parents, since your goal
        # is to eliminate ANY nodes with the same position.
        return self.state == o.state

class Solution(object):
    def __init__(self, node, mem_needed, steps):
        self.node = node
        self.mem_needed = mem_needed
        self.iterations = steps

    def inc(self, other):
        self.node = other.node
        self.mem_needed = max(self.mem_needed, other.mem_needed)
        self.iterations += other.iterations

#...

print_solution("bl", bl_search(board))
# I commented out all but the first search to avoid cluttering up the output.

С этими изменениями код дает решение (я оставлю вам право проверить его правильность, но вот мой вывод).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

* bl
moves: 20
    [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
    [[1, 2, 3], [8, 6, 4], [7, 0, 5]]
    [[1, 2, 3], [8, 6, 4], [0, 7, 5]]
    [[1, 2, 3], [0, 6, 4], [8, 7, 5]]
    [[1, 2, 3], [6, 0, 4], [8, 7, 5]]
    [[1, 0, 3], [6, 2, 4], [8, 7, 5]]
    [[0, 1, 3], [6, 2, 4], [8, 7, 5]]
    [[6, 1, 3], [0, 2, 4], [8, 7, 5]]
    [[6, 1, 3], [2, 0, 4], [8, 7, 5]]
    [[6, 1, 3], [2, 7, 4], [8, 0, 5]]
    [[6, 1, 3], [2, 7, 4], [8, 5, 0]]
    [[6, 1, 3], [2, 7, 0], [8, 5, 4]]
    [[6, 1, 0], [2, 7, 3], [8, 5, 4]]
    [[6, 0, 1], [2, 7, 3], [8, 5, 4]]
    [[6, 7, 1], [2, 0, 3], [8, 5, 4]]
    [[6, 7, 1], [2, 5, 3], [8, 0, 4]]
    [[6, 7, 1], [2, 5, 3], [8, 4, 0]]
    [[6, 7, 1], [2, 5, 0], [8, 4, 3]]
    [[6, 7, 0], [2, 5, 1], [8, 4, 3]]
    [[6, 0, 7], [2, 5, 1], [8, 4, 3]]
    [[6, 5, 7], [2, 0, 1], [8, 4, 3]]
nodes needed: 44282
iterations:   59930

Надеюсь это поможет!

person Henry Keiter    schedule 06.03.2013
comment
я пытался сделать некоторые методы для сравнения, но я начал путаться, используя такие строки, как parentNode = Node(node.state, node.parent) и подобные, и моя программа начала выдавать много ошибок. xD Прежде чем я разочаровался еще больше, я подумал о том, чтобы попросить помощи, чтобы увидеть, есть ли простой способ избавиться от повторяющихся узлов. - person Rasec Noir; 06.03.2013
comment
Действительно? Я попробовал это, и, похоже, это работает без операторов сравнения. - person Waleed Khan; 06.03.2013
comment
Я скопировал его код и добавил наивные методы __eq__ в Board и Node, и, кажется, это работает (глубина продолжает расти, во всяком случае; решение еще не получено). - person Henry Keiter; 06.03.2013
comment
мой друг делает это на java, и его решатель достигает глубины 20 и возвращает решение. dfs, с другой стороны, я думаю, это нормально, что он не даст решения в ближайшее время, но bfs должен работать менее чем за 60 секунд. - person Rasec Noir; 06.03.2013
comment
@WaleedKhan, который работает, потому что это буквально один и тот же объект: когда не определены __cmp__, __eq__ или __ne__, Python сравнивает их по идентификатору объекта. Это работает для фиктивных классов, но не для случая OP. - person Henry Keiter; 06.03.2013
comment
@RasecNoir Я добавил в свой ответ несколько основных операторов сравнения (те, которые я использовал), чтобы помочь вам в этом. Вероятно, также стоит упомянуть, что я еще не видел решения, но оно прошло 22-й уровень и несколько раз возвращалось к 0, так что, вероятно, что-то идет не так и в другом месте. - person Henry Keiter; 06.03.2013
comment
полагаю, что так. если он идет больше 20, это действительно означает, что что-то не так. Думаю, это сложнее, чем я думал. Спасибо за ответ, и я продолжу работать над этим, и посмотрим, сможет ли кто-нибудь еще заставить мой код работать. Думаю, если я решу эту проблему, все мои параметры поиска, кроме BFS, будут работать нормально. - person Rasec Noir; 06.03.2013
comment
@RasecNoir Я выполнил прогон, просто используя первый поиск (я предполагаю, что это BFS, но ваше название могло бы быть немного яснее); это занимает некоторое время, но находит решение за 20 ходов. - person Henry Keiter; 07.03.2013
comment
@HenryKeiter да, это так. извините за то, что название звучит не очень хорошо, но я не англичанин, и мне нравится иногда использовать свои собственные термины xD, я все еще не смог получить какой-то результат. как выглядит окончательный код с вашими изменениями? но да, это правильное решение. я предполагаю, что затем программа попытается выполнить поиск в глубину, и это более проблематично. Я пытался использовать вашу небольшую помощь по коду, но, думаю, у меня это плохо получается. - person Rasec Noir; 07.03.2013
comment
@RasecNoir Ха-ха, я понимаю. Я обновил свой ответ полным текстом моих изменений (я пропустил то, что не изменил). - person Henry Keiter; 07.03.2013
comment
@HenryKeiter Большое спасибо. я попробовал это, и теперь это на глубине номер 17 и работает. Я отмечу ваш ответ как решение, а также попробую завтра одну новую идею, чтобы попытаться сравнить узлы. Ваша помощь спасла меня от головной боли. Большое спасибо за помощь. ^_^ - person Rasec Noir; 07.03.2013