0

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],
            ]))
Sonny49
  • 425
  • 3
  • 18
  • 2
    Could you please indicate what it is you are trying to do? – GregoirePelegrin Jul 06 '22 at 12:45
  • when position is reaching 14 and 14 + 1 is equal length of array which is 15 to stop the recursive call – Sonny49 Jul 06 '22 at 12:47
  • Which loop do you think your program is in? – quamrana Jul 06 '22 at 12:47
  • Your function is recursive; `return` returns back to the immediate caller, not your top level call. – chepner Jul 06 '22 at 12:47
  • trying to solve this kata https://www.codewars.com/kata/551f23362ff852e2ab000037 – Sonny49 Jul 06 '22 at 12:48
  • @chepner looks like I clearly don't understand Python, I wonder how I can break the loop? – Sonny49 Jul 06 '22 at 12:50
  • In function max_value: 1) `return dictionary` seems incorrectly placed in the for loop, and 2) `list` is a bad name for a variable name in Python since it masks the built-in function. – DarrylG Jul 06 '22 at 12:50
  • You need to test the return value of `sliding_triangle` to determine if you should use `break`. The function itself has no control over a loop in the scope it was called from. – chepner Jul 06 '22 at 12:51
  • @chepner I've done that, I tried breaking in the for loop of sliding_triangle but it has no effect as well, it just skips the 14th call – Sonny49 Jul 06 '22 at 12:52
  • @DarrylG agree but it is not helping to solve the main problem – Sonny49 Jul 06 '22 at 12:53
  • I mentioned `max_value` first using the "broken windows theory of coding". 'sliding_triangle` also has issues but would fix `max_value` since `sliding_triangle` depends upon it. However, this solution appears to be brute-force which is a bad idea as mentioned in the problem statement i.e. "brute-force method is a bad idea unless you have a few centuries to waste". – DarrylG Jul 06 '22 at 13:11
  • Why you saying that it is a brute force? I can read and don't want to look like idiot. But you look like idiot since you didn't read what the code does. My solution works for all test scenarios as I can see correct total number each time. I just can't exit loop since I am not good in Python. Are you here to help or you like being a pain in the ass? Your comments doesn't have any value. – Sonny49 Jul 06 '22 at 13:16
  • This has nothing to do with `Python`. Loops and recursion are just about the same in all programming languages. – quamrana Jul 06 '22 at 13:30
  • 1
    @Sonny49 -- if you look at my profile history of answering questions you will see that I have helped many others rather than seek to be a pain in the ass. I immediately saw this as brute-force since it uses a recursive brute force search. An optimal solution for this type of problem uses dynamic programming (which your solution doesn't). Anyway, your tune and unwillingness to take simple feedback make me bid you adieu. – DarrylG Jul 06 '22 at 13:38
  • @DarrylG ok whatever it is, if you are so smart help me break the loop – Sonny49 Jul 06 '22 at 13:41
  • My attempts to break the loop leads to other errors in the code (e.g. index errors out of range in max_value). This could be tracked down but I have trouble following the code. Sorry, you're right this is not a brute-force algorithm but seems to be a greedy algorithm. That will probably not find the optimal sum either. – DarrylG Jul 06 '22 at 14:27
  • @DarrylG got it working finally, the problem was that for loop was in process of doing something instead of stopping, initially my break condition was in for loop right above iterative function call. – Sonny49 Jul 07 '22 at 14:25
  • 1
    @Sonny49 -- that's great. Feel free to post your answer so I can upvote it. Stackoverflow allows you to answer your own questions i.e. [Can I answer my own question](https://stackoverflow.com/help/self-answer#:~:text=Yes!,to%20answer%20their%20own%20questions.) You have to wait 48 hours (2 days) before you can accept your own answer. – DarrylG Jul 07 '22 at 15:16
  • definitely test cases are wrong over there, people are complaining a lot, including 106 4 to be instead of 1074 and this comment: A note for those who failed at the random testcases: make sure you are not using any global variable, as those test cases are actually 5 sub-testcases randomly generated in a for loop, so any global var you're using will not be reset after each test like the sample cases. – Sonny49 Jul 08 '22 at 04:50
  • I also tried my code in another online Python editor it also gives me the correct 1064 answer. Which is another proof that there is something wrong with Codewars environment and tests – Sonny49 Jul 08 '22 at 04:57

4 Answers4

1

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],
            ]))
Sonny49
  • 425
  • 3
  • 18
  • Hi Sony49 -- I upvoted before checking. But, doesn't this give a result of 783 while it should be 1074 [Euler problem description](according to https://projecteuler.net/problem=18)? – DarrylG Jul 07 '22 at 23:22
  • Nope the answer has to be 1064, there is. Mistake in the test case scenario, which you can see in the discussions of Kata. If you run the code the response will 1064 for a big pyramide and 23 for small one. If you could give me the test case you have I can test the code and find the bug if there is any – Sonny49 Jul 08 '22 at 01:57
  • So when you run the code you get 1064 rather than 783 for your code. Interesting. However, my code gives an answer of 1074 which the Euler website reports as correct for problem 18. Also, is your software fast enough to do problem 67 which is the same problem but a much larger triangle i.e. [Euler Problem 67](https://projecteuler.net/problem=67). My code also provides an answer for that problem. – DarrylG Jul 08 '22 at 02:06
  • I found the issue with why I was getting 783 (I was running two test cases, one right after the other). The error was I was not resetting the global variables between test cases. Now I get 1064, but that still is incorrect according to answer on Euler. – DarrylG Jul 08 '22 at 02:12
  • @DarrylG I really recommend you to do the manual calculation and you will get 1064 in your calculator which means that my and your code is correct, that is what I have done and that is what other people done as well. Test case is wrong. Not sure about problem 67 yet, I wasn't aiming to solve it to be honest – Sonny49 Jul 08 '22 at 05:14
  • @DarrylG I also don't think that my code is good, there are many ways to optimise it, I was trying to write it quckly not really worrying about productivity – Sonny49 Jul 08 '22 at 05:15
  • definitely test cases are wrong over there, people are complaining a lot, including 106 4 to be instead of 1074 and this comment: A note for those who failed at the random test cases: make sure you are not using any global variable, as those test cases are actually 5 sub-testcases randomly generated in a for loop, so any global var you're using will not be reset after each test like the sample cases – Sonny49 Jul 08 '22 at 05:16
  • yes, use of global vars is what gave me a inconsistent answers. I post my code so you can check it out. Its really simple but gives answers consistent with Euler 18 & 67. – DarrylG Jul 08 '22 at 08:54
  • updated my answer to find and show the path. It shows the path that produces 1074. – DarrylG Jul 08 '22 at 11:43
1

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 "\033[38;2;{};{};{}m{} \033[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"Path\n {'->'.join(map(str,path))}")
print(f'Highlighted path\n {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

DarrylG
  • 16,732
  • 2
  • 17
  • 23
  • Can you place tell why in the second row you are choosing not the max value? That is probably the reason why you re getting 1074 instead 1064 – Sonny49 Jul 08 '22 at 11:59
  • 75 and then max which is 95 and then moving down choosing highest value. But in you case you re choosing 75 then 64 instead. Maybe I misunderstood the task? – Sonny49 Jul 08 '22 at 12:02
  • @Sonny49 -- the task is to find the overall max path. Using the max nearest you on the next row is an example of [greedy algorithm](https://en.wikipedia.org/wiki/Greedy_algorithm) which works for some problems but not this one. By taking the 95 at the beginning we don't get access to larger value later. It's analogous to being given two options to maximize your happiness: 1) party for 4 years., or 2) college for 4 years, Using a greedy algorithm option 1 seems better. The problem is after 4 years option 2 will present better future options. – DarrylG Jul 08 '22 at 12:32
  • @Sonny49 -- also note the warning in Euler 18: *NOTE: As there are only 16384 routes, it is possible to solve this problem by trying every route. * We see the goal is the maximal route, where a route is a path using adjacent nodes. Choosing max of the nodes nearest you on the next row does not find the max sum. – DarrylG Jul 08 '22 at 12:37
  • @Sonny49 -- simple example where choosing max each row doesn't work: `[ [1], [0, 100], [0, 0, 100], [1000, 0, 0, 0]]` Choosing max row gives 201, but answer is 1001. – DarrylG Jul 08 '22 at 12:41
  • all right, now I understand. I was actually reading today about greedy algorithms, which are not giving the optimal solution and in this case of medium pyramide, I can clearly see that I was wrong and forgot that we can actually get maximum total with a different path. Well I think it is time for me to go back and find a better solution for this problem. I didn't read your code properly to understand it so, will try something else. – Sonny49 Jul 08 '22 at 12:58
  • @Sonny49 -- you can start with my *Recursive Brute Force* solution which has only 4 lines of code. Let me know if you have questions about it. The remaining code is just modifications of this. – DarrylG Jul 08 '22 at 13:03
  • Sure, thank you for your time. I will try to solve it as soon as I can – Sonny49 Jul 08 '22 at 13:20
  • @DarryIG, finally could find some time and solve it to get correct answers and pass tests – Sonny49 Aug 31 '22 at 11:51
0

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
Sonny49
  • 425
  • 3
  • 18
-1

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

  • I tried this approach but it doesn't work. Looks like return doesn't have any effect to the code it is keep looping – Sonny49 Jul 06 '22 at 12:57
  • then, i think there is a structural issue. recursion isn't always better than classic looping. check for the conditions and the counters whether they actually get updated properly – Marco Frag Delle Monache Jul 06 '22 at 13:03
  • counter gets updated properly I check that 100 times. Definitely some structural issue but I can't catch it – Sonny49 Jul 06 '22 at 13:05
  • i suggest you to go in debug mode and check for the full execution in the last case where you want it to stop. just create only one line with an if, put the condition you wish to stop to, place a breakpoint to it, and then place all breakpoints after meanwhile you're stopped – Marco Frag Delle Monache Jul 06 '22 at 13:10
  • 2
    There's no reason to use `pass` instead of simply negating the condition. `if position + 1 != len(triangle): for row in ...`. – chepner Jul 06 '22 at 13:24