How do I break out of this recursive loop?

2019-08-20 09:27发布

问题:

I'm still a newbie in Python, so please tolerate my poor syntax and logic if any were bad. Anyhow, I have a function that I'm trying to cleanly (no fancy moves please) break out of a recursive loop. It's a function in a program that iterate through recursively of 1s and 0s (see input file below), and identify the adjacent 0s as distinct subsets. I have a recursive function called "checkAllInOneDirection" that will loop through each position, going right, left, up, down positions to check for 0s. (It goes only one left deep/further on each of the 4 directions for each recursion).

The problem is for some reason the output of third set should detect only 0,9 and 0,10 as one distinct set but when it breaks out of the recursive after the second set detection, it picked up [0, 4] and [1, 3] at the beginning of the check of the third set... Any help?

This is the output [row, column]:

Distinct subset found :  [[0, 0]]
Distinct subset found :  [[0, 3], [0, 4], [1, 3], [0, 5], [1, 4], [1, 5]]
Distinct subset found :  [[0, 9], [0, 4], [1, 3], [0, 10]]

The correct third subset should be only :

Distinct subset found :  [[0, 9], [0, 10]]

Here's a sample input file:

01100011100100000
11100011111111011
10011111101011011

Here's a snippet of the function, it's called "checkAllInOneDirection":

isItLast = checkLast(forBoo, bakBoo, upBoo, dwnBoo)
if isItLast:
    for each in tempCatch:
        if not each in finalCatch:
            finalCatch.append(each)
    tempCatch=[]
    for each in newCatch:
        if not each in finalCatch:
            finalCatch.append(each)
    newCatch=[]
    return finalCatch, newCatch, columnCount, rowCount, width, height, posToCheck, forBoo, bakBoo, upBoo, dwnBoo
else:
    for each in tempCatch:
        if not each in finalCatch:
            finalCatch.append(each)
    tempCatch =[]
    for each in newCatch:    
        if not each in finalCatch:
            finalCatch.append(each)
            tempCatch.append(each)
    newCatch = []

return checkAllInOneDirection(finalCatch,tempCatch,recursiveCount,newCatch, columnCount, rowCount, width, height, posToCheck, forBoo, bakBoo, upBoo, dwnBoo)    

Here's the entire function, hope it only clarifies not make my question any more confusing:

def checkAllInOneDirection(finalCatch,tempCatch,recursiveCount,newCatch, columnCount, rowCount, width, height, posToCheck, forBoo, bakBoo, upBoo, dwnBoo):
    for each in range (0, len(tempCatch)):
        posToCheck = posToCheckBak = posToCheckUp = posToCheckDwn = [tempCatch[each][0], tempCatch[each][1]]
        newPosForward = checkForward(posToCheck, width)
        if newPosForward != False:
            tempLocale = locale[newPosForward[0]][newPosForward[1]]
        elif newPosForward == False:
            tempLocale = 1
        if newPosForward != False and tempLocale ==0 and not newPosForward in finalCatch and not newPosForward in newCatch:
            forVal = locale[newPosForward[0]][newPosForward[1]]
            newCatch.append(newPosForward)
            posToCheck = newPosForward
            forBoo = True
        elif newPosForward == False and tempLocale == 1 and not newPosForward in newCatch:
            forBoo = False

        newPosBackward = checkBackward(posToCheckBak)
        if newPosBackward != False:
            tempLocale = locale[newPosBackward[0]][newPosBackward[1]]
        elif newPosBackward == False:
            tempLocale = 1    
        if newPosBackward != False and tempLocale ==0 and not newPosBackward in finalCatch and not newPosBackward in newCatch:
            forVal = locale[newPosBackward[0]][newPosBackward[1]]
            newCatch.append(newPosBackward)
            posToCheckBak = newPosBackward
            bakBoo = True
        elif newPosBackward == False and tempLocale == 1 and not newPosBackward in newCatch:
            bakBoo = False

        newPosUp = checkUpRow(posToCheckUp)
        if newPosUp != False:
            tempLocale = locale[newPosUp[0]][newPosUp[1]]
        elif newPosUp == False:
            tempLocale = 1
        if newPosUp != False and tempLocale ==0 and not newPosUp in finalCatch and not newPosUp in newCatch:
            forVal = locale[newPosUp[0]][newPosUp[1]]
            newCatch.append(newPosUp)
            posToCheckUp = newPosUp
            upBoo = True
        elif newPosUp == False and tempLocale == 1 and not newPosUp in newCatch:
            upBoo = False

        newPosDwn = checkDwnRow(posToCheckDwn, height)
        if newPosDwn != False:
            tempLocale = locale[newPosDwn[0]][newPosDwn[1]]
        elif newPosDwn == False:
            tempLocale = 1
        if newPosDwn != False and tempLocale ==0 and not newPosDwn in finalCatch and not newPosDwn in newCatch:
            forVal = locale[newPosDwn[0]][newPosDwn[1]]
            newCatch.append(newPosDwn)
            posToCheckDwn = newPosDwn
            dwnBoo = True
        elif newPosDwn == False and tempLocale == 1 and not newPosDwn in newCatch:
            dwnBoo = False

    isItLast = checkLast(forBoo, bakBoo, upBoo, dwnBoo)
    if isItLast:
        for each in tempCatch:
            if not each in finalCatch:
                finalCatch.append(each)
        tempCatch=[]
        for each in newCatch:
            if not each in finalCatch:
                finalCatch.append(each)
        newCatch=[]
        return finalCatch, newCatch, columnCount, rowCount, width, height, posToCheck, forBoo, bakBoo, upBoo, dwnBoo
    else:
        for each in tempCatch:
            if not each in finalCatch:
                finalCatch.append(each)
        tempCatch =[]
        for each in newCatch:    
            if not each in finalCatch:
                finalCatch.append(each)
                tempCatch.append(each)
        newCatch = []

    return checkAllInOneDirection(finalCatch,tempCatch,recursiveCount,newCatch, columnCount, rowCount, width, height, posToCheck, forBoo, bakBoo, upBoo, dwnBoo)    

回答1:

When using recursion, you really shouldn't be using phrases like "loop" and "break." Instead, think of the problem as consisting of similar subproblems that become trivial at the base cases.

Your general problem is to find 0's that are next to other 0's. (This is called a 4-direction flood fill, by the way.) So the larger problem has identical subproblems; the list of all the connected 0's is the same as the combination of:

  • a list just containing a single "start point" 0
  • a list containing all of the 0's to the right of the "start point" 0
  • a list containing all of the 0's to the left of the "start point" 0
  • a list containing all of the 0's above the "start point" 0
  • a list containing all of the 0's below the "start point" 0

So somewhere in your recursive function, you'll have something to the effect of:

return [[y,x]] + getConnectedZeros(x+1, y) + getConnectedZeros(x-1, y) + getConnectedZeros(x, y+1) + getConnectedZeros(x, y-1)

Knowing this, you need to then think about your base cases, the cases where getConnectedZeros() will have to return something different than the combination of its subproblems' solutions. To me, the base cases are:

  • when the function is called on a place containing a 1
  • when the function is called on a 0 that has already been "found"

For both cases, simply returning an empty list will work since when [] is returned, it is in place of more recursive calls. If these conditions were not included, then the recursion would run forever, and never break hit a base case.

Based on those ideas, here is a solution to your problem:

sampleInput = "01100011100100000\n11100011111111011\n10011111101011011"
inputMatrix = [[int(n) for n in row] for row in sampleInput.split('\n')] #matrix where each row is a list of the numbers from sampleInput

def getConnectedZeros(matrix, x, y, foundIndicies=[]):
    if 0<=y<len(matrix) and 0<=x<len(matrix[y]): #catch out of bounds
        if matrix[y][x] == 1: #catch 1s
            return []
        else:
            if not (x,y) in foundIndicies: #catch 0's we've already "seen"
                foundIndicies.append((x,y))
                return [[y,x]] + getConnectedZeros(matrix, x+1, y, foundIndicies) + getConnectedZeros(matrix, x-1, y, foundIndicies) + getConnectedZeros(matrix, x, y+1, foundIndicies) + getConnectedZeros(matrix, x, y-1, foundIndicies)
            else:
                return []
    else:
        return []


#Now we can just loop through the inputMatrix and find all of the subsets
foundZeroIndicies = []
subsets = []
y = -1
for row in inputMatrix:
    y += 1
    x = -1
    for val in row:
        x += 1
        if (not [y,x] in foundZeroIndicies) and val==0:
            zerosList = getConnectedZeros(inputMatrix, x, y)
            subsets.append(zerosList)
            foundZeroIndicies.extend(zerosList)
for subset in subsets:
    print "Distinct Subset Found  : ", subset

Hopefully that helps some. (And hopefully it was coherent, it's 5 am here...)



回答2:

My code is an example of using the recursive function walk(). I hope it would help you solve the problem.

input = ['01100011100100000',
         '11100011111111011',
         '10011111101011011']
col_len = 17
row_len = 3

walked = []
output = []

def walk(subset_in, row, col):
    if (0 <= row < row_len) and (0 <= col < col_len) and (row, col) not in walked:
        walked.append((row, col))
        if input[row][col] == '0':
            if subset_in is not None:
                subset = subset_in
            else:
                subset = []

            subset.append((row, col))
            walk(subset, row, col+1)
            walk(subset, row+1, col)
            walk(subset, row, col-1)
            walk(subset, row-1, col)

            if subset_in is None:
                output.append(subset)

for row in xrange(row_len):
    for col in xrange(col_len):
        if (row, col) not in walked:
            walk(None, row, col)

for subset in output: print subset


回答3:

to break out of recursion you need to use return. If your recursion continues furthermore, you need to re consider your base case.

just for fun I tried using networkx for this, not that it answers your question:

data = """01100011100100000
11100011111111011
10011111101011011""".splitlines()

import networkx

G = networkx.Graph()
found = set()

for i, row in enumerate(data):
    for j, c in enumerate(row):
        if c == '0':
            found.add((i, j))
            if i + 1 < len(data) and data[i + 1][j] == '0':
                G.add_edge((i, j), (i + 1, j))
            if j + 1 < len(row) and row[j + 1] == '0':
                G.add_edge((i, j), (i, j + 1))

groups = map(list, networkx.connected_component_subgraphs(G))
group_nodes = set(node for group in groups for node in group)
individuals = found - group_nodes

print groups
print individuals

"""
[[(0, 15), (0, 14), (1, 14), (0, 13), (0, 12), (0, 16), (2, 14)], [(1, 3), (1, 4), (1, 5), (0, 5), (0, 4), (0, 3)], [(2, 1), (2, 2)], [(0, 9), (0, 10)]]
set([(0, 0), (2, 11), (2, 9)])
"""