Thursday, June 7, 2012

Project Euler 35

The problem description of Project Euler's 35th problem is as follows:

The number, 197, is called a circular prime because all rotations of the digits: 197, 971, and 719, are themselves prime.
There are thirteen such primes below 100: 2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, 79, and 97.
How many circular primes are there below one million?

I choose to solve this problem in a rather functional way so all you will need to do is get a list of all of the primes below 1 million and a function that simply tells if a prime is a circular prime or not. The method that I used to get the primes is the Sieve of Eratosthenes. This is just about the best way to generate primes for almost any project euler problem as it is very fast for generating the primes up to about 10 million, after that there are faster methods which you can employ if you ever need to. The code for just generating the primes is as follows:




def generate_primes(end):
    primes = range(end)
    for i in range(2,len(primes)):
        if primes[i]: #This if statement never fires if we have already 'crossed' the prime out
            for j in range(2*i,end,i):
                primes[j] = None #crosses the prime out
        if i**2 > end:
            return filter(None,primes[2:]) #This simply filters out all of the Nones in the list           

Next we need the function which tells if a prime is circular. I am going to make the assumption here that the list of primes that I have are all represented as strings, trust me it will be very simple if we make this assumption:

def is_circular(prime):
    for i in range(len(prime)):
        if prime[i:] + prime[:i] not in primes:      
            return 0
    return 1

A refresher on how python handles string splicing: string[2:] will copy starting at the third letter and going to the end of the string while string[:2] will start at the first letter of the string and go to the third letter.

So the first time through the for loops prime[i:] will be prime[0:] which copies the entire string while prime[:0] copies nothing. In this way each letter of the string is rotated to the front and the entire string is matched against our current list of primes. If it ever is not found then false is returned. If the for loop runs all the way through true is returned.

So putting it all together we get:


def generate_primes(end):
    primes = range(end)
    for i ∈ range(2,len(primes)):
        if primes[i]:
            for j ∈ range(2*i,end,i):
                primes[j] = None
        if i**2 > end:
            return filter(None,primes[2:])           
        
def euler35():
    def is_circular(prime):
        for i ∈ range(len(prime)):
            if prime[i:] + prime[:i] ¬∈ primes:      
                return 0
        return 1
        
    primes = map(str,generate_primes(10**6))
    circulars = filter(is_circular,primes)   
        
    return len(circulars)
            
print euler35()
Which works at a horribly inefficient time of 5 minutes... Fortunatly there is a solution. Past the primes 2 and 5 every prime ends in 1, 3, 7 or 9 and because each digit in a circular prime is rotated to the last digit at least once we can throw out every prime that contains a 2, 4, 5, 6, 8 or 0. This turns out to be a substantial number, and not only do we never have to check if these are circular, but when checking to see if a number exists in the list of primes having a smaller list greatly reduces the computational cost. So here is my final solution, which runs in only 2 seconds.
def generate_primes(end):
    primes = range(end)
    for i ∈ range(2,len(primes)):
        if primes[i]:
            for j ∈ range(2*i,end,i):
                primes[j] = None
        if i**2 > end:
            return filter(None,primes[2:])           
        
def euler35():
    def is_circular(prime):
        for i ∈ range(len(prime)):
            if prime[i:] + prime[:i] ¬∈ primes:      
                return 0
        return 1
        
    primes = filter(lambda x: x.count('1') + x.count('3') + x.count('7') + x.count('9') == len(x),map(str,generate_primes(10**6)))
    #idea to filter that junk out attributed to njlytoh in project euler fourms
    circulars = filter(is_circular,primes)   
    circulars += ['2','5'] #these are added because they were originally thrown out
        
    return len(circulars)
            
print euler35()
Hope you enjoyed it!

Monday, June 4, 2012

Project Euler 56

The problem description that Problem 56 gives is as follows:
A googol (10100) is a massive number: one followed by one-hundred zeros; 100100 is almost unimaginably large: one followed by two-hundred zeros. Despite their size, the sum of the digits in each number is only 1.
Considering natural numbers of the form, ab, where a, b < 100, what is the maximum digital sum?
 This problem really shows off how sucinctly python can solve some of the problems, especially without having to worry about producing these insanely large numbers. If this was to be done in c you would have to deal with all sorts of problems with having numbers that are too big, and with java you would have to pull out BigInt. I ended up solving this problem in two lines of python.


from itertools import product

print max(map(lambda x: sum(map(int,str(x[0]**x[1]))), product(range(1,100),range(1,100))))
This solves the problem, and at a skimpy 1 second to boot. However while it is cute to solve problems in one or two lines, it is by no means pretty or easy to understand. So lets expand this a bit and see if we can make it more readable
from itertools import product

# for example this takes in (90,90) returns 360 
sum_of_digits = lambda x: sum(map(int,str(x[0]**x[1])))
#generates a list from (90,90),(90,91) to (99,98),(99,99)
numbers_to_check = product(xrange(90,100),xrange(90,100))

print max(map(sum_of_digits,numbers_to_check))
So the first variable you see turns out to actually be a function. Lambda is a keyword that simply creates a new function on the fly, it is heavily used in functional style programming. If you find this line confusing I will write out an equivilant, but more verbose way of accomplishing the same thing:

def sum_of_digits(x):
   sum = 0
   number = x[0] ** x[1] #Takes the product. ex: (3,2) -> 9
   string = str(number) #turns it into a string ex: 9 -> "9"
   for digit in string:
      sum += int(digit)
   return sum
This may look more familiar, and if the first line was confusing I would highly recommend trying to puzzle out what each part of it does before reading my explination below, it is not too long and figuring something out on your own is one of the most gratifiying parts of learning!






So as you must have realized the for loop must have gone somewhere, that is where the map function comes into play. The map function takes a function as its first argument (here the function int()) and something it can loop over for its second (here a string). Then it applies that function to each of the elements and returns a new list that contains the results. So for example "123" turns into [1,2,3]. Very handy.

The second line, leverages the product function from itertools. hopefully my comment shows what the function does, if it does not please refer to the python documentation, it explains it very well.

And lastly the astute reader will have noticed that I changed from iterating from 1 to 100 to 90 to 100. I made the assumption that the larger the a and b variables are the larger a^b will be, a pretty safe assumption! It turns out that this is correct and my computation now runs in only 0.02 seconds.

As always, any comments or questions are more than welcome.


 EDIT:


Nolen Royalty has commeneted and provided what I believe to be a cleaner solution. It is as follows:

print max(sum(map(int, str(i**j))) for i in range(100) for j in range(100))

Or for people who don't like one liners:
answer = 0
for i in range(100):
   for j in range(100):
      answer = max(sum(map(int, str(i**j))),answer)
print answer

Sunday, April 8, 2012

Project Euler 33

Problem 33 of project euler is a problem that concerns it's self with odd ways of canceling fractions. The example they give is 49/98. If you 'cancel' the 9s you will still get the same number: 4/8 = 49/98. So basically we want to see, first if there is a digit that appears in both the numerator and the denominator. I decided to do this in python like so:
#a, the numerator, b the denominator.
def curious(a,b):
    try:
        c = float(a)/b
        if a/10 == b/10: #(4)9 & (9)8
            if float(a%10)/(b%10) == c:
                return 1
        if a/10 == b%10: #(4)9 & 9(8)
            if float(a%10)/(b/10) == c:
                return 1
        if a%10 == b/10: #4(9) & (9)8
            if float(a/10)/(b%10) == c:
                return 1
        if a%10 == b%10: #4(9) & 9(8)
            if float(a/10)/(b/10) == c:
                return 1
    except: #handles cases where b%10 results in 0
        return 0
    return 0
So hopefully either you know the modular arithmetic or it is clear from context, but basically taking any number %10 will knock off everything but the single digit (11%10 -> 1, 72%10 -> 2, 89%10 -> 9, etc). The second potentially confusing part of the 'curious' function is integer division. Basically this is division where you simply never keep track of anything below a whole number (21/10 -> 2, 53/10 -> 5, etc). This way given any pair of two digits numbers I try every combination where they could have two of the same digits. Then I check to see if the other two digits, the ones that would be remaining if the co-occurring digits were removed equal the original fraction. For example I take in 49 and 98 The third if statement would be triggered and 4/8 would be checked against 49/98. They would have the same result and the function would return 1. So now all we need to do is filter out all of the times that the numbers are both multiple of tens and find all of the fractions that are 'curious'.
list = []            
for a in range(10,99):
    for b in range(a + 1,99):
        if curious(a,b):
            if a%10 and b%10:
                 list.append(reduce_fract(a,b))
This code works because for any number like 30 (a multiple of 10) is a or b then 30%10 will equal 0. 0 is always evaluated to mean False so the if statement is never triggered when either of the numbers are divisable by ten. This removes all of the numbers that are not needed by the problem. Now all we have to do is complete the final piece of the puzzle. They want the product of the four fractions that are 'curious' in their lowest common terms. So we will need to write a function that reduces fractions to their lowest common terms. We can do that by iterating over every number from 2 to the numerator and whenever both of the numbers are divisible by that number we simply divide them both and them keep on checking every digit. This function is as follows:
def reduce_fract(a,b):
    for i in range(2,a + 1):
        if a%i == 0 and b%i == 0:
            a /= i
            b /= i
            return reduce_fract(a,b)
    return (a,b)
So putting it all together we get a run time of just 0.019 seconds, no need to even attempt to optimize this code anymore so here is my first attempt at solving this problem.
def curious(a,b):
    try:
        c = float(a)/b
        if a/10 == b/10:
            if float(a%10)/(b%10) == c:
                return 1
        if a/10 == b%10:
            if float(a%10)/(b/10) == c:
                return 1
        if a%10 == b/10:
            if float(a/10)/(b%10) == c:
                return 1
        if a%10 == b%10:
            if float(a/10)/(b/10) == c:
                return 1
    except:
        return 0
    return 0

def reduce_fract(a,b):
    for i in range(2,a + 1):
        if a%i == 0 and b%i == 0:
            a /= i
            b /= i
            return reduce_fract(a,b)
    return (a,b)

list = []            
for a in range(10,99):
    for b in range(a + 1,99):
        if curious(a,b):
            if a%10 and b%10:
                 list.append(reduce_fract(a,b))

print list
print reduce_fract(reduce(lambda x,y: x*y,map(lambda x: x[0], list)),reduce(lambda x,y: x*y,map(lambda x: x[1], list)))
The last line of this map be a little confusing. Each of the reduce statements's functions are simply multiplying the numerators and denominators together using both lambda calculus and reduce. If you do not understand how these work don't worry too much about them for now. I will be describing them more in depth in a future blog post.

Monday, April 2, 2012

Project Euler 43

Problem 43 of project euler is a strange problem, at least in my eyes. First off it starts talking about pandigital numbers. Pan, of course, means across so we can think of it as across digit numbers, which they define as a number that contains every digit (0-9) once and only once. Given that there are 10 digits here and any permutation of them would be a pandigital number there are 10! (thats 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1) different combinations, about 3 million.

So that is the size of our search space, not incredibly large, but the problem gets trickier. They only want pandigitals which small portions of are divisible by prime numbers. I believe that their example makes it clear exactly what they want.

Take the pandigital 1406357289. Then lets look at a small section of it 3 digits at a time:


  • 1406357289 is divisible by 2
  • 1406357289 is divisible by 3
  • 1406357289 is divisible by 5
  • 1406357289 is divisible by 7
  • 1406357289 is divisible by 11
  • 1406357289 is divisible by 13
  • 1406357289 is divisible by 17
So its clear that this will yield a great deal fewer numbers than 3 million.

So as usual I coded a naive approach to see how it performs and to make sure I can answer the question. I implemented a function that takes in a 10 digit number and checks to see if the digits in index 2 through 4 are divisible by 2, then the digits 3 through 5 are divisible by 3 then.... etc
. Then I simply generated every single permutation of the digits 0-9 and threw away any that did not pass the function, from there you can simply sum up all of the remaining ones and you have your answer.
Hopefully the python makes this clear.


from itertools import permutations

def wtf_problem_43(number):
    primes = [2,3,5,7,11,13,17]
    for i in range(len(primes)):
        if int(number[1 + i:4 + i])%primes[i] != 0:
            return False
    return True

print sum(map(int,filter(wtf_problem_43,map(''.join,permutations("1234568790")))))


This code does indeed work, but it takes 47.6 seconds to execute. Surely we can do better!


First off there is no need to generate every permutation of the digits 0-9. Surely we can cut a large amount of those out of the search space very quickly. Right off the bat what we could do is simply focus on generating only a 3 digit chunk that is divisible by its relevant prime number. It would be like instead of generating 1406357289 we only generate 406, because it is divisible by 2. Then we would only need to try every remaining number (1,2,3,5,7,8,9) in the last slot of 06_ and see if it is divisible by 3.


This turns the problem into a constraint satisfaction problem (sudoku is a famous CSP) and a nice trick for solving these very quickly is to first make choices where it cuts out the large number of potentials. For instance there are 360 diffent ways to start if we start with the ones divisible by 2, but if we start backwards, with different ways to make numbers that are divisible by 17 we only have 44 of them.

So I created a recursive generator that will yield all of the relevant numbers.

This code is significantly more complex, I left some comments, but recursive generators are a little hard to understand. I personally recommend adding the decorator that prints out the arguments that a function gets when it is called. (add this by putting @dump_args before the function declaration).

from itertools import permutations

def generator(divisors,remaining, previous=""):
    if divisors: #if divisors is empty, then only one digit remains
        if previous == "": #This iteration is the starting iteration, it simply recalls its self with every permutation of the last two digits
            for permutation in permutations(remaining,2):
                permutation = ''.join(permutation) #('1','2') -> "12"
                for i in generator(divisors,remaining.translate(None,permutation),permutation):
     #translate strips the used digits, ensuring pandigitality
                    yield i + permutation
        else:
            for permutation in permutations(remaining,1): #unnecessarily complex, I just wanted it to look consistent
                permutation = ''.join(permutation) + previous[:2]
                if int(permutation)%divisors[0] == 0:#
                    for i in generator(divisors[1:],remaining.translate(None,permutation),permutation):
                        yield i + permutation[0]
    else:
        yield remaining

            
print sum(map(int,generator([17,13,11,7,5,3,2],"1234568790")))
Even though this code could still be speed up it solves the problem in just 0.008 seconds! Again, if after looking at the code with the dump_args decorator you still do not understand it, please leave me a comment.

Friday, March 30, 2012

Project Euler 15

In problem 15 of Project Euler the problem of finding how many paths are possible on a 20x20 grid. To start us off project euler provides a very nice image of all of the possible paths through a 2x2 grid:

Hopefully it should be obvious how many paths there are, there are 6 paths that are possible. Surely there is a nice mathematical way to do this, but for now lets think about this as simply a problem to code your way out of. First off all we need to choose a data representation for this problem. It should be pretty obvious that the number of times the arrow goes 'east' for one square is the same as well as the number of times the arrow goes 'south' for one square. So we can then think about this problem as saying we need to make a certain number of 'east' moves and a certain number of 'south' moves. So when we start we have two choices, we can use a 'south' move or a 'east' move. So each time we still have at least one 'south' move and one 'east' move left we have to go down two different paths, but if we don't have any 'east' moves left there can only be one path to go down, same goes for 'south' moves. So we could write a function in python that looks like this:




def f(downs,lefts):
    if downs > 0 and lefts > 0:
        return f(downs - 1,lefts) + f(downs, lefts - 1)
    else:
        return 1

if __name__ == "__main__":
    print f(20,20)

This will solve the problem, but as the size of the grid gets bigger and bigger the number of times that we have to call the funciton increases expotentially. I am not even sure how long it would take to get the answer. But because this function is deterministic, meaning that it will always return the same output given the same inputs, we can use a techinique called memoization. The general plan for what we will do is look up in a hashmap the input every time we run the function and see if we have already run the function on those inputs, if we have we simply output what we got last time.
def memoize(f):
    cache = {}
    def g(x,y):
        if (x,y) not in cache:
            cache[(x,y)] = f(x,y)
        return cache[(x,y)]
    return g

@memoize
def f(downs,lefts):
    if downs > 0 and lefts > 0:
        return f(downs - 1,lefts) + f(downs, lefts - 1)
    else:
        return 1

if __name__ == "__main__":
    print f(20,20)

This code uses something called decorators. Basically what it does it every time the fuction f is called memoize's g function is called instead. So as you can see the g funtion first checks to see if the input is in a cache and then, if it is not, it runs the function and stores the result in the cache. This makes the program execute in less than 0.005 seconds. This is an amazing speed up given that I tried to run the first program when I started writing this blog post and it has still not finished.

Monday, March 26, 2012

Project Euler 37

Problem 37 of Project Euler has to do with finding the intersection of right and left truncatable primes. For example 73 is a prime and if you take off the left most digit, you will still have a prime. Same goes for the right most digit. The problem states that there are only 11 of these such primes. This sounds like an intriguing fact, however you quickly should come to the realization that this is highly dependent on the base in which you are counting.

Take, for instance, base 1, in which you simply make tally marks. So the first four primes would be:

II (2)
III (3)
IIIII (5)
IIIIIII (7)

Obviously there are zero left or right truncatable primes, assuming you do not count 1 as a prime. If you do there are 2. This should be pretty clear because when you truncate there is simply only one number it could be. If you did the same thing with base 2 (binary) then there are two numbers it could be, this should increase the chances of a prime being right truncatable. Here are the primes that are right truncatable (assuming that we stop when we hit 2 or 3):

1011 (11)
111 (7)
101 (5)

So as our intuition predicted, there are more right-truncatable primes here. This actually happens each time you increase the base in which you count. According to The Prime Glossary base 10 has 83 right truncatable primes, and base 42 has 175734. So the number of primes that are both right and left truncatable in a given base is simply somewhat arbitrary so I will not go further into depth about that.

My first attempt at solving this problem approaches it simply by writing a function that determines if a prime is right and left truncatable and simply iterating over all of the primes until I find 11 that pass the function. This takes about 5 minutes and 30 seconds to run. The code first opens a file containing a large number of primes and stores it in an array. Then it simply iterates over every prime, checking with the 'truncatable' function to see if it should add it to the list. Hopefully this python code should be self explanatory.

with open("../primes") as f:
    primes = [x.strip('\n') for x in f.readlines()]

primes = primes[1:]
#1 is contained in the array, we should remove it
def truncatable(number):
    for i in range(1,len(number)):
        if not number[i:] in primes:
            return False
        if not number[:-i] in primes:
            return False
    return True

truncates = set()
for prime in primes:
    if len(prime) > 1:
        if truncatable(prime):
            truncates.add(prime)
    if len(truncates) == 11:
        break

print sum(map(int,truncates))
obviously this is not the most efficient way of doing this. I decided to redo the problem. This time I was going to create the list of right truncatable primes and a list of left truncatable primes. Then whenever they both contain a prime (the intersection of the two sets) I will know that that prime is both right and left truncatable. Creating the lists for left or right truncation is fairly easy. I simply start with the list of single digit primes and then add all of the possible additions to the respective sides of the numbers. Then I filter out all of the non-prime numbers I have obtained and pass on the current list to see if we found any both right and left truncatable primes. This approach works significantly better, as it searches a much smaller amount of numbers, instead of searching through all primes it simply search all of the numbers that could be truncated to a right or left truncatable prime. This approach only takes about 14 seconds, a huge difference in speed. A note on the implementation, if you know what a generator in python is, feel free to skip this. I used a special type of function in python. In the function you will find yield instead of return. I find the best way to think about this is that the function simply spits out the values as it would with return, but it keeps its place in its execution. None of its variables are reset and it simply waits to continue computing until the next values are asked for. If you look at the code and are still having problems understand what is happening here is a great resource on generators.
with open("../primes") as f:
    primes = [x.strip('\n') for x in f.readlines()]

def left_truncatable():
    numbers = ["2","3","5","7"]
    extenders = [str(x) for x in range(1,10)]
    while True:
        temp = []
        for number in numbers:
            for extender in extenders:
                temp.append(extender + number)
        numbers = filter(primes.__contains__, temp)
        yield numbers

def right_truncatable():
    numbers = ["2","3","5","7"]
    extenders = ["1","3","7","9"]
    while True:
        temp = []
        for number in numbers:
            for extender in extenders:
                temp.append(number + extender)
        numbers = filter(primes.__contains__, temp)
        yield numbers

   
finals = set()
rights = right_truncatable()
lefts = left_truncatable()
while len(finals) < 11:
    finals.update(set(rights.next()).intersection(lefts.next()))
    
print sum([int(x) for x in finals])

If any of this code is hard for you to read please leave a comment and I will attempt to resolve the confusion. As an addendum I decided to see if I could speed up the code any more by shrinking the number of primes I have to check against by see if they were simply the same length. This has the ability to speed up the code if the .__contains__ function checks every single element to see if the element it is looking for is there. However the added cost of pre-filtering the primes must have canceled out any gains I saw by only having to check a smaller list and simply added to the complexity of the code, something you should never want. Therefore I am omitting posting the code.

Wednesday, March 21, 2012

Project Euler 67

Project Euler 67 is about finding the maximal path through a series of nodes. In this case it is the sum of integers in a triangle, from top to bottom. This is a simpler version of the problem which the Viterbi Algorithm solves. The Viterbi Algorithm is used primarily in HMMs where you want to find a minimal path through K layers of N nodes, and every node can travel to any of the nodes in the following layers. There is an added complication of there being a transition cost, each different node has a cost of going to another node. A small example, with the transition costs being either 0, 1 or 2. Each depending on how far away the previous number is.

1 3 4 5
7 9 1 2
6 3 8 0

So to solve this the realization that only the preceeding nodes matter for any given node. Therefore we can simplify the problem by running one layer at a time and updating their weights such as:


1 3 4 5
7 9 1 2
6 3 8 0


4 = 3 + max(1+0 or 7+1 or 6+2)
11 = 9 + max(1+1 or 7+0 or 6+1)
5 = 3 + max(1+2 or 7+0 or 6+0)
1 4 4 5
7 11 1 2
6 5 8 0

8 = 4 + max(4+0 or 11+1 or 5+2)
etc
1 4 8 5
7 11 6 2
6 5 13 0

etc
1 4 8 12
7 11 6 8
6 5 13 7

So we can see here that 5 is the lowest possible value, and we only had to visit each node once!

The same can be applied to the simpler problem of the triangle. Again an example is provided below, this time finding the maximal path.
3
7 4
2 4 6
8 5 9 3
3
10 7
2 4 6
8 5 9 3
3
10 7
12 14 13
8 5 9 3
3
10 7
12 14 13
20 19 23 16
So here we can see that the maximal path is 23 any we only had to visit each node once. So now we have a plan, lets implement it in python.

First Implementation:
if __name__ == "__main__":
    numbers = []
    with open("triangle.txt") as f:
        for line in f.readlines():
            numbers.append(map(lambda x: int(x),line.split()))
    for i in range(1,len(numbers)):
        for j in range(len(numbers[i])):
            if j == 0: # on the left side of the triangle
                numbers[i][j] += numbers[i-1][j]
            elif j == len(numbers[i]) - 1: # on the right side of the triangle
                numbers[i][j] += numbers[i-1][j-1]
            else:
                numbers[i][j] += max(numbers[i-1][j-1],numbers[i-1][j])
    print max(numbers[-1])
This code runs in ~ 0.05 seconds. Very fast given that the brute force solution would take about 5 billion years.

So the program essentially first creates an array in which we will store all of the numbers, then we get to this like
          numbers.append(map(lambda x: int(x),line.split()))
This may be a bit confusing for people not accustomed to lambda functions or the map function.

Basically line.split(), if line is "12 5 29" will return ["12","5","29"]. So we want to transform each element from a string to an integer. map(function,list) will run function on each element in a list and return the resulting list. lambda here creates a temporary function that takes in a variable x and applies int(x) to it. This is an example of bad code, I have become too accustomed to using lambda functions each time I use map, so lambda functions are not needed in the slightest. An equivalent line of code would be:

          numbers.append(map(int,line.split()))
Going forward hopefully the code should be self explanatory, if not a bit ugly. I don't like to have so many if statements so I while perusing the forums I found a cleaner way to solve this problem.

if __name__ == "__main__":
    numbers = []
    with open("triangle.txt") as f:
        for line in f.readlines():
            numbers.append([0] + [int(x) for x in line.split()] + [0]) 
            #credit goes to ArhturDenture in the fourms for a more pythonic way
    for i in range(1,len(numbers)):
        for j in range(1,len(numbers[i]) - 1):
            numbers[i][j] += max(numbers[i-1][j-1],numbers[i-1][j])
    print max(numbers[-1])



So here I was able to not only pad the array so I no longer have to have the sanity checks, but I removed the lambda function and the map function. The run time for this is essentially cut in half (0.028 sec)

So first of all the triangle will now look something like this:

0 3 0
0 7 4 0
0 2 4 6 0
0 8 5 9 3 0


This will eliminate the need to check to see if we are on either side of the triangle, so long as we don't modify the sides the interior of the triangle will always be greater than zero, or it will not matter. Secondly we were able to both remove the lambda and the map functions by doing it in a more pythonic way. Basically when there is a for loop inside the brackets for a list the for loop will be executed and stored as a list. This will execute a lot faster as it does not need to either create a function because of the lambda function or execute the map function.