# The number 3797 has an interesting property. Being prime itself,
# it is possible to continuously remove digits from left to right,
# and remain prime at each stage: 3797, 797, 97, and 7. Similarly
# we can work from right to left: 3797, 379, 37, and 3.
 
# Find the sum of the only eleven primes that are both truncatable
# from left to right and right to left.
 
# NOTE: 2, 3, 5, and 7 are not considered to be truncatable
# primes.
    
#----------------------------------------------------------------#
# The idea : 
#
#  1. While eleven truncatable primes have not been found :
#        Test new primes to see if they are truncatable.
#
#  2. Find the sum of the eleven truncatable primes which have
#  been found.
#
#  To implement this in a fast way, we use a slightly modified
#  version of Eratosthenes' sieve, which finds new primes in
#  blocks of suitable size.
#
#----------------------------------------------------------------#



#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
#%%%%%%%%%%%%%%%%%%%%%%   The Solution     %%%%%%%%%%%%%%%%%%%%%%#
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#

# Solve the problem. 
def problem_37() :

    # In the beginning we don't know any truncatable prime
    count = 0
    truncatable_primes = []
    primes_list = []
    
    # Keep looking for truncatable primes till all 11 have been found.
    while (count < 11):
        old_length = len(primes_list)
        
        # Find the primes in the next block of 10000 numbers.
        primes_list = new_primes(primes_list, 10000) 
        new_length = len(primes_list)

        # For each new prime, check if it is truncatable. The
        # single-digit primes are not considered truncatable.
        i = old_length 
        if i < 8 :
            i = 11
            
        while i < new_length : 
            if primes_list[i] : # True if and only if i is prime. 
                # We reuse primes_list in this function instead of
                # computing it again.
                if is_truncatable(i, primes_list) : 
                    truncatable_primes.append(i)
                    count = count + 1
            i = i + 1
    
    print "The truncatable primes are : " + str(truncatable_primes)
    # Return the sum of all the truncatable primes.
    return sum(truncatable_primes)
                         
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
#%%%%%%%%%%%%%%%%%%   Helper Functions     %%%%%%%%%%%%%%%%%%%%%%%#
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#


# Accepts a (possibly empty) list of Boolean (True/False) values
# which has the following property :
#
#   For each index i of the list, list[i] is True if and only if i
#   is a prime number.

# Returns a list which is longer by "count", and has the same
# property as the argument list. Uses Eratosthenes' sieve to
# compute this extension.
def new_primes(primes_list, count) :

    # "Every number is prime until proved composite."
    extension = [True] * count
    
    # list.extend(new_list) is the "big brother" of
    # list.append(new_element).
    primes_list.extend(extension)

    # 0 and 1 are special cases.
    primes_list[0] = False
    primes_list[1] = False
    
    new_length = len(primes_list)
    
    # It is enough to sift using divisors up till the square root.
    limit = int(new_length**(1.0/2)) + 1
    
    # The "sieve" : 
    for i in range(2, limit) :
        if primes_list[i] :
            for j in range(2*i, new_length, i) :
                primes_list[j] = False
                        
    return primes_list

# Returns a list of all the non-empty truncations of the string
# given as the argument.
def all_truncations(string):
        truncations = []
        length = len(string)

        for i in range(1, length):
            truncations.append(string[i:])
            truncations.append(string[:i])
        return truncations

# Accepts a number and a Boolean "list of primes" which contains
# information about all primes up to this number. Checks whether
# the number is truncatable or not.
def is_truncatable(number, primes_list) :
    # We assume that the number is truncatable, till we find
    # evidence against this.
    truncatable = True

    truncations = all_truncations(str(number))

    for string in truncations :
        num = int(string)

        # If this "sub" number is not prime, then the original
        # number (argument) is not truncatable.
        if not primes_list[num] :
            truncatable = False

    return truncatable
    

                
# For testing
print "The sum of all the truncatable primes is : " + str(problem_37())
