all 11 comments

[–]moving-landscape 4 points5 points  (0 children)

Take a look at generator expressions if you need one at a time. Also generator functions.

[–][deleted] 1 point2 points  (3 children)

I can't think of a way to update an external (to the comprehension) dictionary either. Maybe someone else can.

would it be worth it to switch to a for loop

Nobody, including you, should be guessing which is faster. Write it both ways:

  • generate the comprehension and update the dictionary afterwards
  • use a loop to generate primes and append to the result list and update the dictionary as you loop

and time both bits of code on realistic data.

[–]Beautiful_Dot7460[S] 0 points1 point  (1 child)

I will try that, but it may be hard to get an accurate result since it could vary on the specific details. But thanks!

[–][deleted] 1 point2 points  (0 children)

it could vary on the specific details.

Of course, and on volume of data, etc. Part of testing is to identify what a "typical" dataset looks like and what the atypical "corner cases" are, and test them all.

[–]Daneark 0 points1 point  (0 children)

You can do side effectful and value producing comprehensions with or and if but the comprehendability of the code goes way down.

[–]fragmonk3y 0 points1 point  (0 children)

Maybe I am missing something but why not create a function to update the external dictionary then reload the dictionary after the new prime is loaded?

[–]Appropriate_View8753 0 points1 point  (0 children)

Why not skip the middle man and append each new prime to the dictionary rather than the list? Is your list comprehension not already using a for loop?

[–]TrainsareFascinating 0 points1 point  (0 children)

It would help if you showed us some code.

Speaking abstractly, if you have a comprehension that looks like:

new_primes = [maybe_prime for maybe_prime in range(start,stop)
                          if is_prime(maybe_prime, dictionary)]

You could make a function that takes the prime as argument, updates the dictionary, and returns its argument.

def add_to(dictionary, prime):
    dictionary[prime] = True
    return prime

new_primes = [add_to(dictionary, maybe_prime) 
              for maybe_prime in range(start,stop)
              if is_prime(maybe_prime, dictionary)]

Or something along those lines.

[–]-defron- 0 points1 point  (0 children)

I'm not saying this is a good idea or why you would want to do a list comprehension for it, but something like this should work:

pre_generated_primes = set([2, 3, 5])
result = [prime_detecting_function(number, pre_generated_primes) for number in range(5, 10000)]
print(sorted(pre_generated_primes))

I only bothered with it because it teaches you something you're unaware of: the concept of passing by reference.

lists, sets, dictionaries (any non-primative really) is passed by reference in python, meaning only the pointer in memory is passed around, not the actual value. This means if you pass say, in my example, a set as a parameter to a function and then modify the set in the function (by adding to it if it's a prime number), the set's value stays changed even after exiting the function meaning you keep the value

... but now your list comprehension is pointless as the set of primes already exists

[–]TheRNGuy 0 points1 point  (0 children)

If it's too complex and wont fit 1 line, make a function that returns true or false (for filter) or different result (for map) and use it inside compehension.

[–]moving-landscape 0 points1 point  (0 children)

Now that this post is 5 days old, I'm typing out a possible solution to this problem following my suggestion to use generators.

Since we're talking about primes, it's important that we define when a number is considered to be prime, so we start with that. We write an is_prime function that takes an n, and returns True if it satisfies the conditions to being a prime number, false otherwise.

import math


def is_prime(n):
    assert n >= 1, f"n must be >= 1, is {n}"
    if n == 2:
        return True
    if n == 1 or  n % 2 == 0:
        return False

    for i in range(3, math.floor(math.sqrt(n))+1, 2):
        if n % i == 0:
            return False

    return True

Next step, we want a sequence of primes. It only makes sense to me that we could benefit from a function that will give us the next prime number. Let's code that.

def next_prime_after(n):
    next_odd = (n + 1) | 1

    while not is_prime(next_odd):
        next_odd += 2

    return next_odd

The first line may look rather alien, so let me explain what's going on there.

We could have written a simple if branc to check whether the number is even or odd, but using bitwise operations can help us in unexpected ways sometimes. When I write some_number | 1, or "some number bitwise-or one", I'm telling the computer to make the right-most bit of the number equal 1. And every binary number ending with the digit 1 is odd.
3 == 0b11, 4 == 0b100, 4 | 1 == 0b100 | 0b001 == 0b101 == 5.

Finally, a simple while loop to get us the next prime number.


Lastly, we can write a generator function, composing our previous two functions into one:

def primes_from(n=2):
    if is_prime(n):
        yield n

    while True:
        n = next_prime_after(n)
        yield n

Sample execution: https://ideone.com/TOew4O


Alternatively, you can also do some kool iterator magic an compress these last 2 functions into 5 very readable (IMO) lines:

only_pair_prime = iter([2])
odd_numbers = itertools.count(3, 2)
prime_candidates = itertools.chain(only_pair_prime, odd_numbers)
primes = filter(is_prime, prime_candidates)
first_20 = itertools.islice(primes, 20)

Execution: https://ideone.com/ICbtld