0

I am practicing list comprehensions and nested list comprehensions. As part of my practice I am writing out equivalent for loops. This for loop I cannot get right, and I believe it's because I'm trying to assign a value rather than a variable in a function call. The error I receive is:

File "<stdin>", line 4
SyntaxError: can't assign to function call

The code I have written for this loop is:

import math

def squared_primes():
    list = []
    for x in range(1,1000000):
        for q in range(2,math.sqrt(x)+1):
            if all(x % q != 0):
                list.append(x**2)
    print(list)

This function is trying to create a list of perfect squares whose roots are prime numbers in the range 1 to 1000000.

Can someone help me understand where exactly the syntax of my loop breaks down? Also, can I possibly do this as a nested list comprehension? Clearly my list comprehension is breaking down because I can't get my for loop syntax right...


SOLUTION: Thanks to user @Evan, I was able to fix the variable and syntax problems, and took some cues about how to fix the all() statement from this thread.

This code will properly return a list of the squared primes from 1,1000:

def squared_primes():
    list1 = []
    for x in range(1,1000):
        if all(x%q !=0 for q in range(2,int(math.sqrt(x)+1))):
            list1.append(x**2)
    print(list1)
Hanzy
  • 394
  • 4
  • 17
  • Do you mean to be passing a float as the second argument to the range() built-in? – Evan Aug 10 '17 at 13:58
  • @Evan no, I mean to be passing an integer. I tried putting for int(q) in range... but it still wouldn't run. Although maybe I should have left it in since it doesn't seem like it was the error. I will try it again to be sure though. – Hanzy Aug 10 '17 at 14:00
  • Also, I assume that you have indented the squared_primes() function in your code, but forgot to add an extra four spaces when posting here. I can run your code without a function call error but if I remove the indents I obviously get the syntax error for a missing indent. I typecast the math.sqrt(x)+1 expression with int() for testing again to avoid the type error. – Evan Aug 10 '17 at 14:07
  • @Evan sorry I did forget the indentation here. I'll fix it now. Did your list output properly? Did you have to add int(q) before the range function? I can't figure why I'm getting this error... I'm going back to try now. Quitting python in case there's some variable stored in memory bugging it up. – Hanzy Aug 10 '17 at 14:09
  • Just realized, you set list=somevariable. list is a built-in type and should not be used as a variable name. Make it anything else and you will fix your problem there. – Evan Aug 10 '17 at 14:12
  • I carefully checked through the edit history, and no version of the code in this question produces the error that was described. I don't understand why this question was left up, as it is clearly not reproducible. – Karl Knechtel Jun 03 '22 at 21:24

2 Answers2

0

This is pretty concise. List comprehensions are glorious.

def squared_primes(maximum):
    return( [ x**2 for x in range(0,maximum) if all( x % i for i in range(2, x) ) ] )

print(squared_primes(1000000))
Evan
  • 2,120
  • 1
  • 15
  • 20
  • I've updated the code to replace the list variable name as list1, and passed an int(q) to the range function but still get the same error... – Hanzy Aug 10 '17 at 14:20
  • Passing int(q) wont fix your problem, as q would be an integer produced by the range() built-in. You need to typecast int(math.sqrt(x)+1) to fix the float error. Are you still getting the assign to function call error? I'm very confused still because I never got that error running your code. – Evan Aug 10 '17 at 14:22
  • I was still getting the same assign to function call error, but typecasting int(math.sqrt(x)+1) seems to have fixed that, as well as changing the variable names. Thanks for your help! – Hanzy Aug 10 '17 at 14:30
  • I'm looking at your list comprehension now and going to attempt to convert this. I've had some issues with arithmetic functions in my comprehensions before but now that this for loop works I may be able to get it to run. – Hanzy Aug 10 '17 at 14:31
  • You've helped me fix the syntax of the code but my math was a bit wrong and yielding an incorrect list. So I'm fixing that now (clearly x % q != 0, but I hadn't quite hit that mark yet). Now I need to check if all x % q =! 0, and I'm updating the code (updated in the question). But I get error 'bool' object is not iterable. Should I open a new question for this topic? Note: I took pointers on this method from the answer in [this thread](https://stackoverflow.com/questions/11619942/print-series-of-prime-numbers-in-python) – Hanzy Aug 10 '17 at 14:45
  • I don't know why you would be trying to iterate over a bool. If you post code it would probably be quite clear what the problem is. – Evan Aug 10 '17 at 14:57
  • I got it to work with some edits to the code! I will provide an edit / update with the proper math and boolean iteration in the original code. – Hanzy Aug 10 '17 at 14:58
0

This code will properly return a list of the squared primes from 1,1000:

Except that it returns 1 as the first element of the list and 1's square root isn't a prime. Let's fix that glitch and rewrite the code as a proper function:

from math import sqrt

def squared_primes(maximum):
    primes = []

    for number in range(2, maximum):
        if all(number % divisor != 0 for divisor in range(2, int(sqrt(number)) + 1)):
            primes.append(number ** 2)
    return primes

print(squared_primes(1000))

BTW, this is not a list comprehension:

all(x % q !=0 for q in range(2, int(math.sqrt(x) + 1)))

it's a generator! If you wanted a list comprehension you would have done:

all([x % q !=0 for q in range(2, int(math.sqrt(x) + 1))])

but stick with the generator as it fails composites with less effort.

Your code will start to bog down when we ask for a list of the squares up to 1000000 (a million) or more. That's when we'll want a more efficient sieve-based algorithm like:

def squared_primes(maximum):
    sieve = [True] * maximum

    if maximum > 0:
        sieve[0] = False  # zero is not a prime
        if maximum > 1:
            sieve[1] = False # one is not a prime

    for index in range(2, int(maximum ** 0.5) + 1):
        if sieve[index]:
            prime = index
            for multiple in range(prime + prime, maximum, prime):
                sieve[multiple] = False

    return [index * index for index in range(maximum) if sieve[index]]

At around a million, this code will return results about 20x faster than your division-based solution.

And @Evan's glorious comprehension, since it lacks your math.sqrt() optimization, will be orders of magnitude slower than either (I'm still waiting for it to finish for one million) and starts the list with two incorrect results. We can put it on a par time-wise with your revised code by doing:

from math import sqrt

def squared_primes(maximum):
    return [number ** 2 for number in range(2, maximum) if all(number % divisor for divisor in range(2, int(sqrt(number)) + 1))]

print(squared_primes(1000))

And this is a list comprehension. But again, the wrong approach so go back and look at the sieve-based implementation.

cdlane
  • 40,441
  • 5
  • 32
  • 81
  • Oops thank you for that; I was a bit careless and saw that 1 was the first element of the list, but you're right - it's not prime. Good catch! – Hanzy Aug 10 '17 at 18:00
  • I just looked into the sieve algorithm and I'm going to look into this solution, which seems very effective. I initially did ask for integers from my code up to 1000000 and noticed it took some time... – Hanzy Aug 10 '17 at 18:58
  • I'm looking over the sieve algorithm (I went to an outside resource to understand the basics), and trying to figure out the while loop. I get that the number -= set(...) says that start with the first multiple the original prime (2) and count by that prime to the maximum, then subtract from the original full set. What I don't get is how the prime number increases on each iteration. I see that prime = numbers.pop() pulls the value from the unmarked primes, but doesn't this pull from the top of the pile? Would it pop 2 first and then continue with 3 b/c of the while loop? – Hanzy Aug 11 '17 at 03:44
  • I'm just confused because I thought that pop would take an element from the end of the list which would fail to make the next iteration mark new numbers as non-prime. Clearly I must have misunderstood .pop()? – Hanzy Aug 11 '17 at 03:46
  • @Hanzy, two things: first, this isn't `list.pop()`, it's `set.pop()`. That said, `set.pop()` is supposed to return an arbitrary element of the set. But, the current reality, especially as of Python 3.6 where dictionaries & **kwargs maintain their order, is the elements are pop'd in the order they were added to the set. This may not a behavior we can rely on in the future. So, I've replaced the sieve code above with a `list`-based implementation, which slightly outperforms the `set`, but is also slightly more complicated. – cdlane Aug 11 '17 at 06:08