Ask Your Question
1

List creation with local variables

asked 2019-01-02 13:33:53 +0200

logomath gravatar image

updated 2019-01-02 20:28:51 +0200

Can one create a list with some assumptions inside [ ], without having to make loops?

edit retag flag offensive close merge delete

Comments

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?

logomath gravatar imagelogomath ( 2019-01-02 13:35:43 +0200 )edit

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)

logomath gravatar imagelogomath ( 2019-01-02 14:25:00 +0200 )edit

2 Answers

Sort by ยป oldest newest most voted
1

answered 2019-01-02 17:38:37 +0200

rburing gravatar image

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).

edit flag offensive delete link more

Comments

1

Wow! Thank you

logomath gravatar imagelogomath ( 2019-01-02 20:41:24 +0200 )edit
1

answered 2019-01-02 21:25:31 +0200

nbruin gravatar image

updated 2019-01-02 21:30:01 +0200

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()]
edit flag offensive delete link more

Comments

Thank you!

logomath gravatar imagelogomath ( 2019-01-03 09:02:47 +0200 )edit

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Question Tools

1 follower

Stats

Asked: 2019-01-02 13:33:53 +0200

Seen: 358 times

Last updated: Jan 02 '19