-3

https://projecteuler.net/problem=35

All problems on Project Euler are supposed to be solvable by a program in under 1 minute. My solution, however, has a runtime of almost 3 minutes. Other solutions I've seen online are similar to mine conceptually, but have runtimes that are exponentially faster. Can anyone help make my code more efficient/run faster?

Thanks!

#genPrimes takes an argument n and returns a list of all prime numbers less than n
def genPrimes(n):
    primeList = [2]
    number = 3
    while(number < n):
        isPrime = True
        for element in primeList:
            if element > number**0.5:
                break
            if number%element == 0 and element <= number**0.5:
                isPrime = False
                break
        if isPrime == True:
            primeList.append(number)
        number += 2
    return primeList

#isCircular takes a number as input and returns True if all rotations of that number are prime
def isCircular(prime):
    original = prime
    isCircular = True
    prime = int(str(prime)[-1] + str(prime)[:len(str(prime)) - 1])
    while(prime != original):
        if prime not in primeList:
            isCircular = False
            break
        prime = int(str(prime)[-1] + str(prime)[:len(str(prime)) - 1])
    return isCircular

primeList = genPrimes(1000000)
circCount = 0
for prime in primeList:
    if isCircular(prime):
        circCount += 1
print circCount
Martijn Pieters
  • 889,049
  • 245
  • 3,507
  • 2,997

1 Answers1

0

Two modifications of your code yield a pretty fast solution (roughly 2 seconds on my machine):

  1. Generating primes is a common problem with many solutions on the web. I replaced yours with rwh_primes1 from this article:

    def genPrimes(n):
        sieve = [True] * (n/2)
        for i in xrange(3,int(n**0.5)+1,2):
            if sieve[i/2]:
                sieve[i*i/2::i] = [False] * ((n-i*i-1)/(2*i)+1)
        return [2] + [2*i+1 for i in xrange(1,n/2) if sieve[i]]
    

    It is about 65 times faster (0.04 seconds).

  2. The most important step I'd suggest, however, is to filter the list of generated primes. Since each circularly shifted version of an integer has to be prime, the circular prime must not contain certain digits. The prime 23, e.g., can be easily spotted as an invalid candidate, because it contains a 2, which indicates divisibility by two when this is the last digit. Thus you might remove all such bad candidates by the following simple method:

    def filterPrimes(primeList):
        for i in primeList[3:]:
            if '0' in str(i) or '2' in str(i) or '4' in str(i) \
            or '5' in str(i) or '6' in str(i) or '8' in str(i):
                primeList.remove(i)
        return primeList
    

    Note that the loop starts at the fourth prime number to avoid removing the number 2 or 5.

    The filtering step takes most of the computing time (about 1.9 seconds), but reduces the number of circular prime candidates dramatically from 78498 to 1113 (= 98.5 % reduction)!

The last step, the circulation of each remaining candidate, can be done as you suggested. If you wish, you can simplify the code as follows:

circCount = sum(map(isCircular, primeList))

Due to the reduced candidate set this step is completed in only 0.03 seconds.

Community
  • 1
  • 1
Falko
  • 15,326
  • 12
  • 50
  • 91