# List creation with local variables Can one create a list with some assumptions inside [ ], without having to make loops?

edit retag close merge delete

For example in place of

pp=previous_prime;

w=[n for n in prime_range(5,100) if (n+pp(n)+pp(pp(n))).is_prime()];

I wold like to write something like this:

w=[n for n in prime_range(5,100) if (n+m+k).is_prime() "where" m=pp(n) and k=pp(m)];

Is that possible in some way?

I tried this

%timeit

pp=previous_prime;

w=[n for n in prime_range(5,1000) for m in prime_range(n//2,n) for k in prime_range(m//2,m) if m==pp(n) and k==pp(m) and (n+m+k).is_prime()];

(5 loops, best of 3: 861 ms per loop)

It works, but it is way slower than the original here:

%timeit

pp=previous_prime;

w=[n for n in prime_range(5,1000) if (n+pp(n)+pp(pp(n))).is_prime()];

(125 loops, best of 3: 1.61 ms per loop)

Sort by » oldest newest most voted Your question is whether you can create local variables inside an expression (in particular, inside a list comprehension): Is there a Python equivalent of the Haskell 'let'? The answer is basically no, unless you acknowledge abominations like this:

w=[n for n in prime_range(5,10000) if (lambda m: (lambda k: (n+m+k).is_prime())(previous_prime(m)))(previous_prime(n))]


Let me turn your original list comprehension into a function:

def my_primes0(lower, upper):
return [n for n in prime_range(lower,upper) if (n+previous_prime(n)+previous_prime(previous_prime(n))).is_prime()]


You create a list of primes in a range with prime_range, but subsequently you use previous_prime which does new superfluous primality tests. You can make better use of the list returned byprime_range like so:

def my_primes1(lower, upper):
p = prime_range(lower, upper)
for i in range(len(p)-2):
if is_prime(p[i] + p[i+1] + p[i+2]):
yield p[i+2]


This is a generator which you can use to create a list as follows:

sage: w1 = list(my_primes1(5, 10000))
sage: w1 == w
True


These are the timings on my machine (I increased the upper bound to get into the millisecond range):

sage: timeit('my_primes0(5,10000)', number=125, repeat=3)
125 loops, best of 3: 35.8 ms per loop
sage: timeit('list(my_primes1(5, 10000))', number=125, repeat=3)
125 loops, best of 3: 6.16 ms per loop


In general, if you want to use local variables in the definition of an iterator then you should probably define a generator (using yield).

more

1

Something like that has very recently been added to Python 3, in the form of "expression assignment" :=, but that would only help you once sage has migrated to Python 3. There is a workaround by using nested "for" clauses in list or iterator comprehensions:

w=[n for n in prime_range(5,100) for m in [pp(n)] for k in [pp(m)] if (n+m+k).is_prime() ];


I would not recommend it in this case because using "previous_prime" is a very expensive way of getting the previous prime if you're enumerating them in order anyway!

Given that prime_range returns a list anyway, there's no problem using it as such. If it were an iterator that lazily produces the values, there'd be a benefit in processing the generator to produce a sliding window:

from collections import deque
def iter_window(iterable,n):
T=iter(iterable)
result=deque((next(T) for i in range(n)),n)
yield tuple(result)
for t in T:
# the deque has max length n, so appending an
# element automatically drops the leftmost entry.
result.append(t)
yield tuple(result)


so that you can do

w=[n for k,m,n in iter_window(prime_range(2,100),3) if (n+m+k).is_prime()]

more