Minimum path sum in a triangle (Project Euler 18 and 67) with Python Project Euler 18
Project Euler 67  

As problem 67 is harder, I'll go with that one:


  By starting at the top of the triangle below and moving to adjacent
  numbers on the row below, the maximum total from top to bottom is 23.

             3
            7 4
           2 4 6
          8 5 9 3

  
  That is, 3 + 7 + 4 + 9 = 23.
  
  Find the maximum total from top to bottom in triangle.txt (right click
  and 'Save Link/Target As...'), a 15K text file containing a triangle
  with one-hundred rows.
  
  NOTE: This is a much more difficult version of Problem 18. It is not
  possible to try every route to solve this problem, as there are 2^99
  altogether! If you could check one trillion (10^12) routes every second
  it would take over twenty billion years to check them all. There is an
  efficient algorithm to solve it. ;o)


I solved this using dynamic programming. Here's my code:

def max_triangle_sum(triangle, height):
    if height == 1:
        return triangle[0][0]

    dp = [[-1] * row_index for row_index in range(1, height + 1)]

    dp[0][0] = triangle[0][0]

    for i in range(1, height):
        dp[i][0] = dp[i-1][0] + triangle[i][0]
        dp[i][i] = dp[i-1][i-1] + triangle[i][-1]

        for j in range(1, i):
            dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j]

    return max(dp[-1])

if __name__ == '__main__':
    height = None

    while True:
        try:
            height = int(input('Please enter height of the triangle: '))

            if height &lt;= 0:
                raise Exception('Height cannot be negative or 0')

            break

        except Exception as e:
            print(f'Error occurred: {e}')
            print()

    triangle = []

    for row_index in range(1, height + 1):
        while True:
            try:
                row = list(map(int, input(f'Please enter {row_index} space seperated integer(s): ').split()))

                if len(row) != row_index:
                    raise Exception(f'Exactly {row_index} numbers required')

                if any(number &lt; 0 for number in row):
                    raise Exception(f'Numbers cannot be negative')

                triangle.append(row)

                break

            except Exception as e:
                print(f'Error occurred: {e}')
                print()

    print('The maximum triangle sum is: ')
    print(max_triangle_sum(triangle, height))


This runs quite fast, considering it would actually take 20 billion years (If your computer can compute 10^12 operations in 1 second) to solve using brute force. But still, it doesn't look that good

How do I improve this code?
General

Since the problem gives the triangle as a text file, your program should probably be able to read that file. It would be quite a lot of work to input them manually, and when using the file like in python triangle.py &lt; triangle.txt a first row with the number of lines needs to be added. This reading function can be quite simple:

def read_triangle(file_name):
    with open(file_name) as f:
        return [list(map(int, line.split())) for line in f]


Algorithm

As for the actual algorithm, I'm not quite sure what yours does. Which means that it is not very readable :). Not having any documentation, like a docstring does not help either.

The easiest algorithm for this is a bottom up approach that reduces each line into the maximum sum reachable from the line above it.

def reduce_rows(row, row_below):
    """Reduces two consecutive rows to the maximum reachable sum from the top row."""
    return [max(row_below[i], row_below[i+1]) + row[i] for i in range(len(row))]

def max_sum(triangle):
    """Find the maximum sum reachable in the triangle."""
    row_below = triangle.pop()
    for row in reversed(triangle):
        row_below = reduce_rows(row, row_below)
        # print(row_below)
    return row_below[0]

if __name__ == "__main__":
    triangle = read_triangle("triangle.txt")
    print(max_sum(triangle))


Style

It's a good thing that you are already using a if __name__ == "__main__": guard. You also seem to be mostly following Python's official style-guide, PEP8. The only thing you could improve on that part is to reduce the number of blank lines. While some help the readability, parts that are logically connected should be in one block:

height = None
while True:
    try:
        height = int(input('Please enter height of the triangle: '))
        if height &lt;= 0:
            raise Exception('Height cannot be negative or 0')
        break
    except Exception as e:
        print(f'Error occurred: {e}\n')

triangle = []
for row_index in range(1, height + 1):
    while True:
        try:
            row = list(map(int, input(f'Please enter {row_index} space seperated integer(s): ').split()))
            if len(row) != row_index:
                raise Exception(f'Exactly {row_index} numbers required')
            elif any(number &lt; 0 for number in row):
                raise Exception(f'Numbers cannot be negative')
            else:
                triangle.append(row)
                break
        except Exception as e:
            print(f'Error occurred: {e}\n')


I also put the separate exceptions and the happy path into an if..elif..else structure. This is not really needed (the other cases are never reachable if an exception is raised), but IMO it helps with readability. It also make it easier if at some point you decide that those should just print a message directly, instead of raising an exception that is immediately caught only to be printed. You could make those print statements directly, which would allow you to be more specific about the exception you expect. This makes it so that all other exceptions stop the program (as they should):

while True:
    try:
        height = int(input('Please enter height of the triangle: '))
    except ValueError as e:
        print(f'Error occurred: {e}\n')
    if height &gt; 0:
        break
    else:
        print('Error occurred:  Height cannot be negative or 0')

triangle = []
for row_index in range(1, height + 1):
    while True:
        try:
            row = list(map(int, input(f'Please enter {row_index} space separated integer(s): ').split()))
        except ValueError as e:
            print(f'Error occurred: {e}\n')
        if len(row) != row_index:
            print(f'Error occurred:  Exactly {row_index} numbers required')
        elif any(number &lt; 0 for number in row):
            print(f'Error occurred:  Numbers cannot be negative')
        else:
            triangle.append(row)
            break


Better user input

This is actually a good place to implement a function asking for user input and validating it. We need type validation and input validation here, so this should suffice:

def ask_user(message, type_=str, validate=None, non_valid_msg=""):
    while True:
        try:
            user_input = type_(input(message))
        except ValueError as e:
            print(f"Expected {type_}")
            continue
        if validate is not None:
            if validate(user_input):
                return user_input
            else:
                print(non_valid_msg)
        else:
            return user_input


Which makes your code a lot easier:

def list_of_int(s):
    return list(map(int, s.split()))

if __name__ == "__main__":
    height = ask_user("Please enter height of the triangle: ", int,
                      lambda h: h &gt; 0, "Height must be positive")
    triangle = [ask_user(f"Please enter {n} space separated integer(s): ", list_of_int,
                         lambda l: len(l) == n, f"Exactly {n} numbers required")
                for n in range(1, height + 1)]
    ...