Skip to main content
added 5 characters in body
Source Link
Barry
  • 18.4k
  • 1
  • 40
  • 92

Just a couple things:

sieve_list = list(range(end+1))

You don't actually need your list to be [0, 1, 2, ... ]. You just need an indicator of whether or not it's true or false. So it's simpler just to start with:

sieve_list = [True] * (end + 1)

That'll likely perform better as well.

When you're iterating over multiples of primes, you're using:

range(each_number*2, end+1, each_number)

But we can do better than each_number*2, we can start at each_number*each_number. Every multiple of that prime between it and its square will already have been marked composite (because it will have a factor smaller than each_number). That'll save a steadily larger increment of time each iteration.

As an optimization, we know up front that 2 and 3 are primes. So we can start our iteration at 5 and ensure that we only consider each_number to not be multiples of 2 or 3. That is, alternate incrementing by 4 and 2. We can write this function:

def candidate_range(n):
    cur = 5
    incr = 2
    while cur < n+1:
        yield cur
        cur += incr
        incr ^= 6 # or incr = 6-incr, or however

Full solution:

def sieve(end):
    prime_list = [2, 3]
    sieve_list = [True] * (end+1)
    for each_number in candidate_range(end):
        if sieve_list[each_number]:
            prime_list.append(each_number)
            for multiple in range(each_number*each_number, end+1, each_number):
                sieve_list[multiple] = False
    return prime_list

Impact of various changes with end at 1 million, run 10 times:

initial solution       6.34s
[True] * n             3.64s (!!)
Square over double     3.01s
candidate_range        2.46s

Also, I would consider every_multiple_of_the_prime as an unnecessary long variable name, but YMMV.

Just a couple things:

sieve_list = list(range(end+1))

You don't actually need your list to be [0, 1, 2, ... ]. You just need an indicator of whether or not it's true or false. So it's simpler just to start with:

sieve_list = [True] * (end + 1)

That'll likely perform better as well.

When you're iterating over multiples of primes, you're using:

range(each_number*2, end+1, each_number)

But we can do better than each_number*2, we can start at each_number*each_number. Every multiple of that prime between it and its square will already have been marked composite (because it will have a factor smaller than each_number). That'll save a steadily larger increment of time each iteration.

As an optimization, we know up front that 2 and 3 are primes. So we can start our iteration at 5 and ensure that we only consider each_number to not be multiples of 2 or 3. That is, alternate incrementing by 4 and 2. We can write this function:

def candidate_range(n):
    cur = 5
    incr = 2
    while cur < n+1:
        yield cur
        cur += incr
        incr ^= 6 # or incr = 6-incr, or however

Full solution:

def sieve(end):
    prime_list = [2, 3]
    sieve_list = [True] * (end+1)
    for each_number in candidate_range(end):
        if sieve_list[each_number]:
            prime_list.append(each_number)
            for multiple in range(each_number*each_number, end+1, each_number):
                sieve_list[multiple] = False
    return prime_list

Impact of various changes with end at 1 million, run 10 times:

initial solution       6.34s
[True] * n             3.64s
Square over double     3.01s
candidate_range        2.46s

Also, I would consider every_multiple_of_the_prime as an unnecessary long variable name, but YMMV.

Just a couple things:

sieve_list = list(range(end+1))

You don't actually need your list to be [0, 1, 2, ... ]. You just need an indicator of whether or not it's true or false. So it's simpler just to start with:

sieve_list = [True] * (end + 1)

That'll likely perform better as well.

When you're iterating over multiples of primes, you're using:

range(each_number*2, end+1, each_number)

But we can do better than each_number*2, we can start at each_number*each_number. Every multiple of that prime between it and its square will already have been marked composite (because it will have a factor smaller than each_number). That'll save a steadily larger increment of time each iteration.

As an optimization, we know up front that 2 and 3 are primes. So we can start our iteration at 5 and ensure that we only consider each_number to not be multiples of 2 or 3. That is, alternate incrementing by 4 and 2. We can write this function:

def candidate_range(n):
    cur = 5
    incr = 2
    while cur < n+1:
        yield cur
        cur += incr
        incr ^= 6 # or incr = 6-incr, or however

Full solution:

def sieve(end):
    prime_list = [2, 3]
    sieve_list = [True] * (end+1)
    for each_number in candidate_range(end):
        if sieve_list[each_number]:
            prime_list.append(each_number)
            for multiple in range(each_number*each_number, end+1, each_number):
                sieve_list[multiple] = False
    return prime_list

Impact of various changes with end at 1 million, run 10 times:

initial solution       6.34s
[True] * n             3.64s (!!)
Square over double     3.01s
candidate_range        2.46s

Also, I would consider every_multiple_of_the_prime as an unnecessary long variable name, but YMMV.

added 201 characters in body
Source Link
Barry
  • 18.4k
  • 1
  • 40
  • 92

Just a couple things:

sieve_list = list(range(end+1))

You don't actually need your list to be [0, 1, 2, ... ]. You just need an indicator of whether or not it's true or false. So it's simpler just to start with:

sieve_list = [True] * (end + 1)

That'll likely perform better as well.

When you're iterating over multiples of primes, you're using:

range(each_number*2, end+1, each_number)

But we can do better than each_number*2, we can start at each_number*each_number. Every multiple of that prime between it and its square will already have been marked composite (because it will have a factor smaller than each_number). That'll save a steadily larger increment of time each iteration.

As an optimization, we know up front that 2 and 3 are primes. So we can start our iteration at 5 and ensure that we only consider each_number to not be multiples of 2 or 3. That is, alternate incrementing by 4 and 2. We can write this function:

def candidate_range(n):
    cur = 5
    incr = 2
    while cur < n+1:
        yield cur
        cur += incr
        incr ^= 6 # or incr = 6-incr, or however)

Full solution:

def sieve(end):
    prime_list = [2, 3]
    sieve_list = [True] * (end+1)
    for each_number in candidate_range(end):
        if sieve_list[each_number]:
            prime_list.append(each_number)
            for multiple in range(each_number*each_number, end+1, each_number):
                sieve_list[multiple] = False
    return prime_list

Impact of various changes with end at 1 million, run 10 times:

initial solution       6.34s
[True] * n             3.64s
Square over double     3.01s
candidate_range        2.46s

Also, I would consider every_multiple_of_the_prime as an unnecessary long variable name, but YMMV.

Just a couple things:

sieve_list = list(range(end+1))

You don't actually need your list to be [0, 1, 2, ... ]. You just need an indicator of whether or not it's true or false. So it's simpler just to start with:

sieve_list = [True] * (end + 1)

That'll likely perform better as well.

When you're iterating over multiples of primes, you're using:

range(each_number*2, end+1, each_number)

But we can do better than each_number*2, we can start at each_number*each_number. Every multiple of that prime between it and its square will already have been marked composite (because it will have a factor smaller than each_number). That'll save a steadily larger increment of time each iteration.

As an optimization, we know up front that 2 and 3 are primes. So we can start our iteration at 5 and ensure that we only consider each_number to not be multiples of 2 or 3. That is, alternate incrementing by 4 and 2. We can write this function:

def candidate_range(n):
    cur = 5
    incr = 2
    while cur < n+1:
        yield cur
        cur += incr
        incr ^= 6 # or incr = 6-incr, or however)

Full solution:

def sieve(end):
    prime_list = [2, 3]
    sieve_list = [True] * (end+1)
    for each_number in candidate_range(end):
        if sieve_list[each_number]:
            prime_list.append(each_number)
            for multiple in range(each_number*each_number, end+1, each_number):
                sieve_list[multiple] = False
    return prime_list

Also, I would consider every_multiple_of_the_prime as an unnecessary long variable name, but YMMV.

Just a couple things:

sieve_list = list(range(end+1))

You don't actually need your list to be [0, 1, 2, ... ]. You just need an indicator of whether or not it's true or false. So it's simpler just to start with:

sieve_list = [True] * (end + 1)

That'll likely perform better as well.

When you're iterating over multiples of primes, you're using:

range(each_number*2, end+1, each_number)

But we can do better than each_number*2, we can start at each_number*each_number. Every multiple of that prime between it and its square will already have been marked composite (because it will have a factor smaller than each_number). That'll save a steadily larger increment of time each iteration.

As an optimization, we know up front that 2 and 3 are primes. So we can start our iteration at 5 and ensure that we only consider each_number to not be multiples of 2 or 3. That is, alternate incrementing by 4 and 2. We can write this function:

def candidate_range(n):
    cur = 5
    incr = 2
    while cur < n+1:
        yield cur
        cur += incr
        incr ^= 6 # or incr = 6-incr, or however

Full solution:

def sieve(end):
    prime_list = [2, 3]
    sieve_list = [True] * (end+1)
    for each_number in candidate_range(end):
        if sieve_list[each_number]:
            prime_list.append(each_number)
            for multiple in range(each_number*each_number, end+1, each_number):
                sieve_list[multiple] = False
    return prime_list

Impact of various changes with end at 1 million, run 10 times:

initial solution       6.34s
[True] * n             3.64s
Square over double     3.01s
candidate_range        2.46s

Also, I would consider every_multiple_of_the_prime as an unnecessary long variable name, but YMMV.

Source Link
Barry
  • 18.4k
  • 1
  • 40
  • 92

Just a couple things:

sieve_list = list(range(end+1))

You don't actually need your list to be [0, 1, 2, ... ]. You just need an indicator of whether or not it's true or false. So it's simpler just to start with:

sieve_list = [True] * (end + 1)

That'll likely perform better as well.

When you're iterating over multiples of primes, you're using:

range(each_number*2, end+1, each_number)

But we can do better than each_number*2, we can start at each_number*each_number. Every multiple of that prime between it and its square will already have been marked composite (because it will have a factor smaller than each_number). That'll save a steadily larger increment of time each iteration.

As an optimization, we know up front that 2 and 3 are primes. So we can start our iteration at 5 and ensure that we only consider each_number to not be multiples of 2 or 3. That is, alternate incrementing by 4 and 2. We can write this function:

def candidate_range(n):
    cur = 5
    incr = 2
    while cur < n+1:
        yield cur
        cur += incr
        incr ^= 6 # or incr = 6-incr, or however)

Full solution:

def sieve(end):
    prime_list = [2, 3]
    sieve_list = [True] * (end+1)
    for each_number in candidate_range(end):
        if sieve_list[each_number]:
            prime_list.append(each_number)
            for multiple in range(each_number*each_number, end+1, each_number):
                sieve_list[multiple] = False
    return prime_list

Also, I would consider every_multiple_of_the_prime as an unnecessary long variable name, but YMMV.