0

This is a question regarding a code challenge, please don't supply too much code.. I'd like to figure this out myself as much as possible.

I recently started getting into code challenges, and combined it with learning Python (I'm a frontend javascript developer by day ;) ). All is going well so far and I'm convinced that this is the best way to learn a new language (for me at least).

I'm currently stuck on a challenge that requires me to print all prime numbers in a given range, this is all done by simple Stdin and Stdout.

I've tried two approaches so far, both are working but are too slow.. below is a link and the code of my fastest implementation so far. Maybe I'm missing something super obvious that is causing my python script to slow down. Currently it takes 1.76s for a single test case with a range of 1, 100000

http://ideone.com/GT6Xxk (you can debug the script here as well)

from sys import stdin
from math import sqrt, ceil

next(stdin) # skip unecessary line that describes the number of test cases

def is_prime(number):
    initial_divider = sqrt(number)

    if number == 2:
      return True
    elif number % 2 == 0 or int(initial_divider) == initial_divider:
      return False

    for divider in range(ceil(initial_divider), 1, -1):
        if number % divider == 0:
            return False

    return True

for line in stdin:
    low, high = [int(x) for x in line.split(' ')]
    primes = [number for number
                     in range(max(low, 2), high+1)
                     if is_prime(number)]

    for prime in primes:
        print (prime)
    print('')

The description of the 'assignment' / challenge is as follows:

Input

The input begins with the number t of test cases in a single line (t<=10). In >each of the next t lines there are two numbers m and n (1 <= m <= n <= >1000000000, n-m<=100000) separated by a space.

Output

For every test case print all prime numbers p such that m <= p <= n, one number >per line, test cases separated by an empty line.

Update 1: I cleaned up the logic of the last block, where the gathering of primes and printing is done:

for line in stdin:
    low, high = [int(x) for x in line.split(' ')]
    for number in range(max(low, 2), high+1):
        if is_prime(number):
            print (number)
    print('')
Community
  • 1
  • 1
ajeurissen
  • 85
  • 9

6 Answers6

1

Change list comprehension to generator, the script will run faster.

for number in range(max(low, 2), high+1):
    if is_prime(number):
        yield number
Delimitry
  • 2,987
  • 4
  • 30
  • 39
LetzerWille
  • 5,355
  • 4
  • 23
  • 26
  • Thanks for the feedback, I thought generators are only more efficient if you need a infinite stream because of memory allocation. I'll give the generator approach a try ! – ajeurissen Sep 20 '15 at 12:14
1

1) It might be dominated by console IO, printing the output. I changed the output so it uses a generator to collect the primes, convert the numbers to strings, and join the numbers with newlines. This should save some memory in list building and push some Python list iteration down into the Python runtime. That made it ~30% faster in unscientific rushed testing on my PC, doesn't make much difference on ideone. (This might be because I bodged it to run in Python 2, which has some very different iteration/list/generator workings, but used Python 3 on ideone).

2) You run the if number == 2: return True test every time; out of the first 100,000 numbers, most of them aren't 2. I extracted that to print 2 before printing the rest of the primes. Very minor change, not really worth it.

3) Your range counts down - range(ceil(initial_divider), 1, -1) - and that's really weird. It's very much more likely that a number will divide by 3 than by, say, 19. Every third number divides by 3, only every 19th number divides by 19. So for quick-return of the function, try the small dividers first, right? I set it to count up. Noticable speed improvement, I hope it's still working.

That's ~50% of the original runtime, in a casual and completely uncomparable situation. Code now looks like this:

from sys import stdin
from math import sqrt, ceil

next(stdin) # skip unecessary line

def is_prime(number):
    initial_divider = sqrt(number)

    if number % 2 == 0 or int(initial_divider) == initial_divider:
      return False

    for divider in range(2, ceil(initial_divider)+1):
        if number % divider == 0:
            return False

    return True

for line in stdin:
    low, high = [int(x) for x in line.split(' ')]
    primes = '\n'.join(str(number) for number
                     in range(max(low, 3), high+1)
                     if is_prime(number))

    if low <= 2: print(2)
    print (primes)
    print('')
TessellatingHeckler
  • 27,511
  • 4
  • 48
  • 87
  • 2
    Another improvement would be to only test odd dividers in the `for` loop, since you test if the number's even before the loop starts. – PM 2Ring Sep 20 '15 at 07:51
  • @TesselatingHeckler, Thanks for the thorough reply! this was the kind of reply I was hoping for, one that breaks my code down and tells me what could be improved upon / is inlogical etc. i tried the '\n'.join approach before but forgot that you need to convert those numbers to strings ;) and your right the range countdown is so weird now that I think of it.. was feeling a bit too sleepy when I came up with that lols ! I'll try to implement the improvements you suggested and let you know if it's fast enough :D – ajeurissen Sep 20 '15 at 12:17
  • ugh detected a bug in my is_prime function.. somehow it returns true if I feed it the number 21.. real weird.. I'm debugging it as we speak. I'll keep you guys up to date. – ajeurissen Sep 20 '15 at 13:28
1

In a language like C or C++ the SPOJ PRIME 1 problem can easily be solved by brute force, i.e. by writing code that sieves all numbers up to 1000,000,000 in less than a second and thus stays below the time limit. Perhaps even in Java, C# or Delphi. But if it is possible in Python at all then it is probably bloody hard and requires some serious fu.

Note, however, that SPOJ PRIME 1 does not ask for a billion numbers to be sieved; it asks for a couple of small ranges to be sieved which are no wider than 100001 numbers, and it likely queries only a handful of ranges. Let's say the number of ranges is 100 (it's probably much less) and the average width is 100000 (it's probably much less) then that's still only 10,000,000 numbers. Sieving the full billion in that situation does two orders of magnitude too much work, which is why SPOJ PRIME 1 manages to weed out the chaff with such precision despite the wide range of languages employed by pundits.

Hence the trick is to do only what's asked - to sieve the ranges provided as input. Even the most simple, straightforward code can do that with lots of time to spare (C++: about a millisecond total). The principle is exactly the same as in my answer to the challenge of drawing 100 random primes from a range with an upper bound of 1,000,000,000, and the same solution applies. The key is writing a function that can sieve a given range (window) without having to sieve all numbers below as well.

Besides, the question of how to beat SPOJ PRIME 1 has been asked numerous times already and the answers given are still valid. A small selection:

Community
  • 1
  • 1
DarthGizka
  • 4,347
  • 1
  • 24
  • 36
0
def PrimesBelow(limit):
    np=set()
    p=[2]
    for i in xrange(1,limit+1,2):
            if i == 1: continue
            if i in np: continue
            beg=2 if i % 2 == 0 else 0
            for j in xrange(beg,int(limit)+1,i):
                    np.add(j)
            p.append(i)
    return i

LetzerWille was right. Function above will return a list of prime numbers below (limit). I think that this function will run faster than checking each number is prime or not, because this function will remove multiplies of each number and add them to (np). Side note: this function will test odd numbers only.

0

Simple improvement. It was embarrasing to see this simple code. Mine was much longer and slower :( ... but I learned a lot :)

Adding also simple function for measuring time mt()

def PrimesBelow(limit):
    np=set()
    p=[2]
    for i in range(3,limit+1,2):
            if i in np: continue
            beg = i*i
            for j in range(beg,int(limit)+1,i):
                    np.add(j)
            p.append(i)
    return p

def mt(n):
    import time
    t = time.time()
    pr = PrimesBelow(n)
    print("#-Primes: {}, Time: {}".format(len(pr), round(time.time()-t, 4)))
    return pr

pr = mt(100000000)

is about 49 secs on a i7-3770 with 16GB

0

This will be the optimized code with less number of executions, it can calculate and display 10000 prime numbers within a second. it uses the prime number property that * if a number is not divisible by the numbers which are less than its square root then it is prime number. * instead of checking till the end(Means 1000 iteration to figure out 1000 is prime or not) we can end the loop within 35 iterations, * break the loop if it is divided by any number at the beginning(if it is even loop will break on first iteration, if it is divisible by 3 then 2 iteration) so we iterate till the end only for the prime numbers

remember one thing you can still optimize the iterations by using the property *if a number is not divisible with the prime numbers less than that then it is prime number but the code will be too large, we have to keep track of the calculated prime numbers, also it is difficult to find a particular number is a prime or not, so this will be the Best logic or code

import math
number=1
count = 0



while(count<10000):
    isprime=1
    number+=1
    for j in range(2,int(math.sqrt(number))+1):
        if(number%j==0):
            isprime=0   
            break
    if(isprime==1):
        print(number,end=" ")
        count+=1    
print("\nCount "+str(count))
Vijay
  • 1
  • 4
  • You can pass any positive Value to n, and remove While loop, You will get whether it is prime or not – Vijay Dec 27 '19 at 03:28