List creation with local variables
Can one create a list with some assumptions inside [ ], without having to make loops?
Can one create a list with some assumptions inside [ ], without having to make loops?
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
).
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()]
Please start posting anonymously - your entry will be published after you log in or create a new account.
Asked: 2019-01-02 13:33:53 +0100
Seen: 452 times
Last updated: Jan 02 '19
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)