Бесконечный цикл и рекурсия в Python

Я работаю над реализацией итеративного поиска с углублением в глубину, чтобы найти решения для проблемы-головоломки 8 . Я не заинтересован в поиске самих фактических путей поиска, а просто в том, чтобы определить, сколько времени требуется для запуска программы. (Я еще не реализовал функцию синхронизации).

Тем не менее, у меня возникли некоторые проблемы при попытке реализовать настоящую функцию поиска (прокрутите вниз, чтобы увидеть). Я вставил весь код, который у меня был до сих пор, поэтому, если вы скопируете и вставите его, вы также сможете его запустить. Это может быть лучший способ описать проблемы, с которыми я сталкиваюсь... Я просто не понимаю, почему я получаю бесконечные циклы во время рекурсии, например. в тесте для головоломки 2 (p2), где первое расширение должно дать решение. Я подумал, что это может быть как-то связано с тем, что не добавлено «Возврат» перед одной из строк кода (это прокомментировано ниже). Когда я добавляю возврат, я могу пройти тест для головоломки 2, но что-то более сложное, например головоломка 3, терпит неудачу, поскольку кажется, что теперь код расширяет только самую левую ветвь...

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

#Classic 8 puzzle game
#Data Structure: [0,1,2,3,4,5,6,7,8], which is the goal state. 0 represents the blank
#We also want to ignore "backward" moves (reversing the previous action)

p1 = [0,1,2,3,4,5,6,7,8]
p2 = [3,1,2,0,4,5,6,7,8]
p3 = [3,1,2,4,5,8,6,0,7]

def z(p):   #returns the location of the blank cell, which is represented by 0
    return p.index(0)

def left(p):
    zeroLoc = z(p)
    p[zeroLoc] = p[zeroLoc-1]
    p[zeroLoc-1] = 0
    return p

def up(p):
    zeroLoc = z(p)
    p[zeroLoc] = p[zeroLoc-3]
    p[zeroLoc-3] = 0
    return p

def right(p):
    zeroLoc = z(p)
    p[zeroLoc] = p[zeroLoc+1]
    p[zeroLoc+1] = 0
    return p

def down(p):
    zeroLoc = z(p)
    p[zeroLoc] = p[zeroLoc+3]
    p[zeroLoc+3] = 0
    return p

def expand1(p):   #version 1, which generates all successors at once by copying parent
    x = z(p)
    #p[:] will make a copy of parent puzzle
    s = []  #set s of successors

    if x == 0:
        s.append(right(p[:]))
        s.append(down(p[:]))
    elif x == 1:
        s.append(left(p[:]))
        s.append(right(p[:]))
        s.append(down(p[:]))
    elif x == 2:
        s.append(left(p[:]))
        s.append(down(p[:]))
    elif x == 3:
        s.append(up(p[:]))
        s.append(right(p[:]))
        s.append(down(p[:]))
    elif x == 4:
        s.append(left(p[:]))
        s.append(up(p[:]))
        s.append(right(p[:]))
        s.append(down(p[:]))
    elif x == 5:
        s.append(left(p[:]))
        s.append(up(p[:]))
        s.append(down(p[:]))   
    elif x == 6:
        s.append(up(p[:]))
        s.append(right(p[:]))
    elif x == 7:
        s.append(left(p[:]))
        s.append(up(p[:]))
        s.append(right(p[:]))
    else:   #x == 8
        s.append(left(p[:]))
        s.append(up(p[:]))

    #returns set of all possible successors
    return s

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

def DFS(root, goal):    #iterative deepening DFS
    limit = 0
    while True:
        result = DLS(root, goal, limit)
        if result == goal:
            return result
        limit = limit + 1

visited = []

def DLS(node, goal, limit):    #limited DFS
    if limit == 0 and node == goal:
        print "hi"
        return node
    elif limit > 0:
        visited.append(node)
        children = [x for x in expand1(node) if x not in visited]
        print "\n limit =", limit, "---",children   #for testing purposes only
        for child in children:
            DLS(child, goal, limit - 1)     #if I add "return" in front of this line, p2 passes the test below, but p3 will fail (only the leftmost branch of the tree is getting expanded...)
    else:
        return "No Solution"

#Below are tests

print "\ninput: ",p1
print "output: ",DFS(p1, goal)

print "\ninput: ",p2
print "output: ",DLS(p2, goal, 1)
#print "output: ",DFS(p2, goal)

print "\ninput: ",p3
print "output: ",DLS(p3, goal, 2)
#print "output: ",DFS(p2, goal)

person Amaranthine    schedule 27.10.2012    source источник
comment
За последние 2 дня было не менее 3 других вопросов о 8-головоломках. Попробуйте выполнить поиск. Особенно вопрос Эшвина кажется очень похожим на ваш.   -  person Junuxx    schedule 28.10.2012
comment
просто примечание, python имеет очень ограниченную глубину рекурсии. Так что используйте рекурсию с умом   -  person Aniket Inge    schedule 28.10.2012
comment
Может ли кто-нибудь объяснить, почему (или указать мне правильное направление), почему узел не возвращается в функции DLS для p2, даже если оператор if выполняется? Однако узел возвращается в p1 (где входные данные — это просто решение).   -  person Amaranthine    schedule 28.10.2012


Ответы (2)


Непосредственная проблема, с которой вы столкнулись с рекурсией, заключается в том, что вы ничего не возвращаете, когда нажимаете на свой рекурсивный шаг. Однако безусловное возвращение значения из первого рекурсивного вызова также не сработает, так как не гарантируется, что первый потомок найдет решение. Вместо этого вам нужно проверить, какой (если есть) рекурсивный поиск, который вы выполняете в своих дочерних состояниях, успешен. Вот как я бы изменил конец вашей функции DLS:

    for child in children:
        child_result = DLS(child, goal, limit - 1)
        if child_result != "No Solution":
            return child_result

# note, "else" removed here, so you can fall through to the return from above
return "No Solution"

Чуть более "питоновский" (и более быстрый) способ сделать это - использовать None в качестве контрольного значения, а не строку "Нет решения". Тогда ваш тест будет просто if child_result: return child_result, и вы можете при желании опустить оператор возврата для неудачных поисков (поскольку None является возвращаемым значением функции по умолчанию).

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

person Blckknght    schedule 27.10.2012
comment
Спасибо, что нашли время подробно объяснить, почему этот способ правильный! - person Amaranthine; 28.10.2012

Используйте классы для своих состояний! Это должно сделать вещи намного проще. Чтобы вы начали. Не хочу публиковать все решение прямо сейчас, но это значительно упрощает задачу.

#example usage
cur = initialPuzzle
for k in range(0,5): # for 5 iterations. this will cycle through, so there is some coding to do
    allsucc = cur.succ() # get all successors as puzzle instances
    cur = allsucc[0] # expand first                                                                                           
    print 'expand ',cur 

import copy


class puzzle:

    '''
    orientation
    [0, 1, 2
     3, 4, 5
     6, 7, 8]
    '''

    def __init__(self,p):
        self.p = p

    def z(self):  
        ''' returns the location of the blank cell, which is represented by 0 '''
        return self.p.index(0)

    def swap(self,a,b):
        self.p[a] = self.p[b]
        self.p[b] = 0

    def left(self):
        self.swap(self.z(),self.z()+1) #FIXME: raise exception if not allowed

    def up(self):
        self.swap(self.z(),self.z()+3)

    def right(self):
        self.swap(self.z(),self.z()-1)

    def down(self):
        self.swap(self.z(),self.z()-3)

    def __str__(self):
        return str(self.p)

    def copyApply(self,func):
        cpy = self.copy()
        func(cpy)
        return cpy

    def makeCopies(self,s):
        ''' some bookkeeping '''
        flist = list()
        if 'U' in s:
            flist.append(self.copyApply(puzzle.up))
        if 'L' in s:
            flist.append(self.copyApply(puzzle.left))
        if 'R' in s:
            flist.append(self.copyApply(puzzle.right))
        if 'D' in s:
            flist.append(self.copyApply(puzzle.down))

        return flist

    def succ(self):
        # return all successor states for this puzzle state
        # short hand of allowed success states
        m = ['UL','ULR','UR','UDR','ULRD','UDL','DL','LRD','DR']
        ss= self.makeCopies(m[self.z()]) # map them to copies of puzzles
        return ss


    def copy(self):
        return copy.deepcopy(self)



# some initial state
p1 = [0,1,2,3,4,5,6,7,8]

print '*'*20
pz = puzzle(p1)
print pz

a,b = pz.succ()
print a,b
person RParadox    schedule 27.10.2012