Why my loop is not exiting when after if condition I return

Question:

In this code I need to exit loop on certain condition. if position + 1 == len(triangle)
Maybe I am not good at Python and don’t understand clearly its behaviour.

It is not listening to my command and keep calling same function instead of leaving the loop.
The only other thing I tried is to call break in the loop itself when same condition is met but it is not working as well.

def max_value(list, index):
    for _ in range(len(list)): 
        dictionary = dict()
        maximum = max(list[index], list[index + 1])
        dictionary['max'] = maximum
        if maximum == list[index]:
            dictionary['next_index'] = index
        else:
            dictionary['next_index'] = index + 1
        
        return dictionary

total = 0
index = 0
skip = False
position = 0
    
def sliding_triangle(triangle): 
    global total
    global index
    global skip 
    global position
    
    if not skip:
        skip = True
        total += triangle[0][0]
        total += max_value(triangle[1], index).get("max")
        index = max_value(triangle[1], index).get("next_index")
        position = 2
        sliding_triangle(triangle)
    
    if position + 1 == len(triangle): return <-----------------HERE I AM EXPECTING IT TO EXIT
        
    for row in range(position, len(triangle)):
        values = max_value(triangle[row], index)
        total += values.get("max")
        index = values.get("next_index")
        print(position, int(len(triangle)), index, values.get("max"), total)
        position += 1
        
        sliding_triangle(triangle)
            
    return total


print(sliding_triangle([
            [75],
            [95, 64],
            [17, 47, 82],
            [18, 35, 87, 10],
            [20,  4, 82, 47, 65],
            [19,  1, 23, 75,  3, 34],
            [88,  2, 77, 73,  7, 63, 67],
            [99, 65,  4, 28,  6, 16, 70, 92],
            [41, 41, 26, 56, 83, 40, 80, 70, 33],
            [41, 48, 72, 33, 47, 32, 37, 16, 94, 29],
            [53, 71, 44, 65, 25, 43, 91, 52, 97, 51, 14],
            [70, 11, 33, 28, 77, 73, 17, 78, 39, 68, 17, 57],
            [91, 71, 52, 38, 17, 14, 91, 43, 58, 50, 27, 29, 48],
            [63, 66,  4, 68, 89, 53, 67, 30, 73, 16, 69, 87, 40, 31],
            [ 4, 62, 98, 27, 23,  9, 70, 98, 73, 93, 38, 53, 60,  4, 23],
            ]))
Asked By: Sonny49

||

Answers:

You can try using an else and a pass, like so:

def max_value():
    # code

def sliding_triangle():
    if not skip:
        # code
    if position + 1 == len(triangle):
        pass
    else:
        for row in range(position, len(triangle)):
            # code
    return total

print sliding_triangle()

As far as I know, you can’t interrupt a def by throwing a return in two or more different points of the script just like in Java. Instead, you can just place a condition that, whether is respected, you skip to the return. Instead, you continue with the execution.

I synthesized your code to let you understand the logic easier, but it’s not a problem if I have to write it fully

Hehey, Got it working finally, so the solution was to break from loop earlier.
I had to put the condition in the beginning of the loop otherwise it was doing the same process and condition was wrong.

total = 0
index = 0
skip = False
position = 0

def max_value(list, index):
    for _ in range(len(list)): 
        dictionary = dict()
        maximum = max(list[index], list[index + 1])
        dictionary['max'] = maximum
        if maximum == list[index]:
            dictionary['next_index'] = index
        else:
            dictionary['next_index'] = index + 1
        
        return dictionary
    
def sliding_triangle(triangle): 
    global total
    global index
    global skip 
    global position
    
    if not skip:
        skip = True
        total += triangle[0][0]
        total += max_value(triangle[1], index).get("max")
        index = max_value(triangle[1], index).get("next_index")
        position = 2
        sliding_triangle(triangle)
        
    for row in range(position, len(triangle)):
        if position == int(len(triangle)): break  <<<--------------- I HAD TO CALL BREAK EARLIER, OTHERWISE FOR LOOP WAS KEEP WORKING INSTEAD OF STOPPING 
        values = max_value(triangle[row], index)
        total += values.get("max")
        index = values.get("next_index")
        position += 1
        sliding_triangle(triangle)
    return total


print(sliding_triangle([
            [75],
            [95, 64],
            [17, 47, 82],
            [18, 35, 87, 10],
            [20,  4, 82, 47, 65],
            [19,  1, 23, 75,  3, 34],
            [88,  2, 77, 73,  7, 63, 67],
            [99, 65,  4, 28,  6, 16, 70, 92],
            [41, 41, 26, 56, 83, 40, 80, 70, 33],
            [41, 48, 72, 33, 47, 32, 37, 16, 94, 29],
            [53, 71, 44, 65, 25, 43, 91, 52, 97, 51, 14],
            [70, 11, 33, 28, 77, 73, 17, 78, 39, 68, 17, 57],
            [91, 71, 52, 38, 17, 14, 91, 43, 58, 50, 27, 29, 48],
            [63, 66,  4, 68, 89, 53, 67, 30, 73, 16, 69, 87, 40, 31],
            [ 4, 62, 98, 27, 23,  9, 70, 98, 73, 93, 38, 53, 60,  4, 23],
            ]))
Answered By: Sonny49

Recursive brute force solution

def sliding_triangle(triangle, row = 0, index = 0):
    if row >= len(triangle) or index >= len(triangle[row]):
        return 0      # row or index out of bounds
    
    # Add parent value to max of child triangles
    return triangle[row][index] + max(sliding_triangle(triangle, row+1, index), sliding_triangle(triangle, row+1, index+1))

Tests

print(sliding_triangle([[3], [7, 4], [2, 4, 6], [8, 5, 9, 3]]))
# Output: 23

print(sliding_triangle([
            [75],
            [95, 64],
            [17, 47, 82],
            [18, 35, 87, 10],
            [20,  4, 82, 47, 65],
            [19,  1, 23, 75,  3, 34],
            [88,  2, 77, 73,  7, 63, 67],
            [99, 65,  4, 28,  6, 16, 70, 92],
            [41, 41, 26, 56, 83, 40, 80, 70, 33],
            [41, 48, 72, 33, 47, 32, 37, 16, 94, 29],
            [53, 71, 44, 65, 25, 43, 91, 52, 97, 51, 14],
            [70, 11, 33, 28, 77, 73, 17, 78, 39, 68, 17, 57],
            [91, 71, 52, 38, 17, 14, 91, 43, 58, 50, 27, 29, 48],
            [63, 66,  4, 68, 89, 53, 67, 30, 73, 16, 69, 87, 40, 31],
            [ 4, 62, 98, 27, 23,  9, 70, 98, 73, 93, 38, 53, 60,  4, 23],
            ]))
# Output: 1074

However, brute force approach times out on larges dataset

Optimized Solution

Apply memoization to brute force solution.

  • Uses cache to avoid repeatedly solving for subpaths of a parent triangle node

Code

def sliding_triangle(triangle):
    ' Wrapper setup function '
    def sliding_triangle_(row, index):
        ' Memoized function which does the calcs'
        if row >= len(triangle) or index >= len(triangle[row]):
            return 0
        if not (row, index) in cache:
             # Update cache
             cache[(row, index)] = (triangle[row][index] + 
                                    max(sliding_triangle_(row+1, index), 
                                        sliding_triangle_(row+1, index+1)))
        return cache[(row, index)]
    cache = {}     # init cache
    return sliding_triangle_(0, 0)  # calcuate starting from top most node

Tests

Find and Show Optimal Path*

  • Modify Brute Force to Return Path
  • Show highlighted path in triangle

Code

####### Main function
def sliding_triangle_path(triangle, row = 0, index = 0, path = None):
    '''
        Finds highest scoring path (using brute force)
    '''
    if path is None:
        path = [(0, 0)]                   # Init path with top most triangle node

    if row >= len(triangle) or index >= len(triangle[row]):
        path.pop()                        # drop last item since place out of bounds
        return path
    
    # Select best path of child nodes
    path_ = max(sliding_triangle_path(triangle, row+1, index, path + [(row+1, index)]), 
           sliding_triangle_path(triangle, row+1, index+1, path + [(row+1, index+1)]), 
           key = lambda p: score(triangle, p))
    
    return path_

####### Utils
def getter(x, args):
    '''
        Gets element of multidimensional array using tuple as index
        Source (Modified): https://stackoverflow.com/questions/40258083/recursive-itemgetter-for-python
    '''
    try:
        for k in args:
            x = x[k]
        return x
    
    except IndexError:
        return 0

def score(tri, path):
    ' Score for a path through triangle tri '
    return sum(getter(tri, t) for t in path)


def colored(r, g, b, text):
    '''
        Use rgb code to color text'
        Source: https://www.codegrepper.com/code-examples/python/how+to+print+highlighted+text+in+python
    '''
    return "33[38;2;{};{};{}m{} 33[38;2;255;255;255m".format(r, g, b, text)

def highlight_path(triangle, path):
    ' Created string that highlight path in red through triangle'
    result = ""                  # output string
    for p in path:               # Looop over path tuples
        row, index = p        
        values = triangle[row]   # corresponding values in row 'row' of triangle
        
        # Color in red path value at index, other values are in black (color using rgb)
        row_str = ' '.join([colored(255, 0, 0, str(v)) if i == index else colored(0, 0, 0, str(v)) for i, v in enumerate(values)])
        result += row_str + 'n'
        
    return result

Test

# Test
triangle = ([
            [75],
            [95, 64],
            [17, 47, 82],
            [18, 35, 87, 10],
            [20,  4, 82, 47, 65],
            [19,  1, 23, 75,  3, 34],
            [88,  2, 77, 73,  7, 63, 67],
            [99, 65,  4, 28,  6, 16, 70, 92],
            [41, 41, 26, 56, 83, 40, 80, 70, 33],
            [41, 48, 72, 33, 47, 32, 37, 16, 94, 29],
            [53, 71, 44, 65, 25, 43, 91, 52, 97, 51, 14],
            [70, 11, 33, 28, 77, 73, 17, 78, 39, 68, 17, 57],
            [91, 71, 52, 38, 17, 14, 91, 43, 58, 50, 27, 29, 48],
            [63, 66,  4, 68, 89, 53, 67, 30, 73, 16, 69, 87, 40, 31],
            [ 4, 62, 98, 27, 23,  9, 70, 98, 73, 93, 38, 53, 60,  4, 23],
            ])


path = sliding_triangle_path(triangle)
print(f'Score: {score(tri, path)}')
print(f"Pathn {'->'.join(map(str,path))}")
print(f'Highlighted pathn {highlight_path(tri, path)}')

Output

Score: 1074
Path
 (0, 0)->(1, 1)->(2, 2)->(3, 2)->(4, 2)->(5, 3)->(6, 3)->(7, 3)->(8, 4)->(9, 5)->(10, 6)->(11, 7)->(12, 8)->(13, 8)->(14, 9)

Highlighted path

Answered By: DarrylG

Got my own correct answer for the kata, which can handle big triangles and passed all tests

def longest_slide_down(triangle):
    temp_arr = []
    first = triangle[-2]
    second = triangle[-1]
    
    if len(triangle) > 2:
        for i in range(len(first)):
            for _ in range(len(second)):
                summary = first[i] + max(second[i], second[i + 1])
                temp_arr.append(summary)
                break
            
        del triangle[-2:]
        triangle.append(temp_arr)
        return longest_slide_down(triangle)

    summary = triangle[0][0] + max(triangle[1][0], triangle[1][1])
    return summary
Answered By: Sonny49
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.