reset password
Author Message
rabbott
Posts: 1649
Posted 12:18 Feb 04, 2019 |

We didn't have a chance to talk about many alternative Python versions for the Goldbach problem. Here's mine. It should open in Google Colabratory (Jupyter). When I run it, it gets the answer in about 0.17 sec.

Look in particular at how it generates the list of primes. It generates only the ones it needs and then keeps the ones it generates -- so it doesn't have to generate the same prime more than once.

Also, notice that to avoid traversing the list twice, isAPrime uses dropwhile rather than takewhile.

 

Last edited by rabbott at 12:55 Feb 04, 2019.
rabbott
Posts: 1649
Posted 13:08 Feb 04, 2019 |

What would happen if you changed the last three lines from

    answer = list(islice(goldbachCounterExamples(), 2))
print(f'Finished in {round((time() - start_time)/n, 3)} seconds/run.')
print(f'Answer: {answer}')

to 

    answer = islice(goldbachCounterExamples(), 2)
print(f'Finished in {round((time() - start_time)/n, 3)} seconds/run.')
print(f'Answer: {list(answer)}')

 

AAdryel
Posts: 8
Posted 20:05 Feb 04, 2019 |

The time to find the Goldbach counter examples should decrease since the conversion to a list is happening after the timer stops. The change should be small, though, since there are only two elements to convert into a list.

jpatel77
Posts: 44
Posted 20:53 Feb 04, 2019 |
rabbott wrote:

We didn't have a chance to talk about many alternative Python versions for the Goldbach problem. Here's mine. It should open in Google Colabratory (Jupyter). When I run it, it gets the answer in about 0.17 sec.

Look in particular at how it generates the list of primes. It generates only the ones it needs and then keeps the ones it generates -- so it doesn't have to generate the same prime more than once.

Also, notice that to avoid traversing the list twice, isAPrime uses dropwhile rather than takewhile.

 

I forgot to point out in class but the primality check can be optimized further. Instead of looking for prime number in a linear fashion, we can just check if any prime number <= sqrt(number being checked) exists which is also a factor of the number to be checked (because n*n<=k for any n,k >= 1). This helped a lot in my haskell code, reduced the time down to 0.30 sec, and your code as well performed faster (clocked about 0.12 sec). Even further, we can check for primes only for 6k+1 and 6k-1 numbers (except for 2 and 3), and maybe can perform the primality check by doing binary search over the primes' list. Haven't worked on these though.

Last edited by jpatel77 at 20:54 Feb 04, 2019.
rabbott
Posts: 1649
Posted 10:59 Feb 05, 2019 |

Jay, What you said sounds interesting, but I'm not completely following it. Would you mind posting your code (or your revision of my code) so that I can see what you did. Thanks.

Anyone else who wants to show off their work is also encouraged to post it.

Last edited by rabbott at 11:30 Feb 05, 2019.
jpatel77
Posts: 44
Posted 12:04 Feb 05, 2019 |

Sure. Here is my haskell code. I will post your code too once I will have an access to my system.

Let me try again on explanation part :D

If p * q = N then EITHER p and q will be on the opposite side of the sqrt(N) OR p==q==sqrt(N)

Let N = 36, then these are the possibilities

  • p = 3, q = 12; Therefore, p < sqrt(36) < q
  • p = 6, q = 6; Therefore, p == q == sqrt(36)

Therefore, it it sufficient to say that there must be one factor of a number N that is less than or equal to sqrt(N). And so, our primality test becomes 

isPrime n = null [p | p <- takeWhile(<= iSqrt n) primes, n `mod` p == 0]

Another way to perform isPrime is to perform binary search over the existing array of prime numbers as long as the largest number in the array is bigger than or equals to the number being checked. This will cut down this check to lg(N). Haven't tried this yet.

 

 

rabbott
Posts: 1649
Posted 12:29 Feb 05, 2019 |

My smallPrimeDivisors uses the square-root idea. (It squares the number rather than taking the square root.)

smallPrimeDivisors n = [d | d <- takeWhile (\x -> x^2 <= n) primes, n `mod` d == 0]

The problem with binary search is (as you say) that the number being tested (as a possible Goldbach counterexample) is always larger than the largest prime generated so far. If you are interested, here's the current version of my Haskell code.

So what did you do so that my code ran faster?

One improvement might be to look only at newly generated primes. (Offhand, I'm not sure how to do that in Haskell. It might be easier in Python.)

rabbott
Posts: 1649
Posted 12:31 Feb 05, 2019 |
AAdryel wrote:

The time to find the Goldbach counter examples should decrease since the conversion to a list is happening after the timer stops. The change should be small, though, since there are only two elements to convert into a list.

Did you try it? I think you will be surprised at the difference in the time.

rabbott
Posts: 1649
Posted 13:30 Feb 05, 2019 |

Jay was right that my Python version didn't limit the check by the square root. Adding that check and also adding code that looks only at new primes when searching for Goldbach counterexamples, the Collaboratory version now runs in about 0.145 sec. (I'm surprised that adding the feature that only new primes are checked didn't help that much.) Here's the current version. (It's the same file as before -- just edited. The older link should still work.)

Last edited by rabbott at 13:36 Feb 05, 2019.
jpatel77
Posts: 44
Posted 13:33 Feb 05, 2019 |

In your code, there is a prime check in primes_gen():

is_prime = not any(p for p in Primes.primes_list if k % p == 0)

I replaced this and limited the check till sqrt(k) only. 

Edit: Didn't see above comment until I submitted this

Last edited by jpatel77 at 13:37 Feb 05, 2019.
rabbott
Posts: 1649
Posted 13:57 Feb 05, 2019 |

 

A couple of additional seemingly minor changes.

  • Compete Primes.primesList up to the current candidate before checking to see if it has Goldbach factors.
  • Now no need to limit the search in hasGoldbachFactors for primes to those less than the candidate. So just look through Primes.primesList.

The result is that the Colab running time is down to 0.115 sec.

Last edited by rabbott at 14:31 Feb 05, 2019.
kmarlis
Posts: 35
Posted 14:58 Feb 05, 2019 |

I took a different approach for the Python version than most of the people I've talked to. The idea is to iterate through odd numbers (not from a list of odd numbers) and maintain a list of primes (rather than generating one) and a list of "disprove numbers". A while loop with a check for the length of the list of "disprove numbers" is what keeps the code from checking for more than 2 "disprove numbers".

Here's the code. It isn't very elegant or complex (it doesn't use anything from itertools) but it averages out to running in 0.09 seconds.

jpatel77
Posts: 44
Posted 15:03 Feb 05, 2019 |
kmarlis wrote:

I took a different approach for the Python version than most of the people I've talked to. The idea is to iterate through odd numbers (not from a list of odd numbers) and maintain a list of primes (rather than generating one) and a list of "disprove numbers". A while loop with a check for the length of the list of "disprove numbers" is what keeps the code from checking for more than 2 "disprove numbers".

Here's the code. It isn't very elegant or complex (it doesn't use anything from itertools) but it averages out to running in 0.09 seconds.

This was by far the best idea I’ve seen Kevin. Didn’t examine much but from what you explained in class, it was cleverly thought out.

kmarlis
Posts: 35
Posted 15:16 Feb 05, 2019 |

Thanks Jay! 

rabbott
Posts: 1649
Posted 16:39 Feb 05, 2019 |

Yet another simplification, which also runs in 0.09 sec. Perhaps it's conceptually similar to Kevin's.

Last edited by rabbott at 16:42 Feb 05, 2019.
kmarlis
Posts: 35
Posted 16:49 Feb 05, 2019 |

Your code actually runs faster (0.07 seconds) when I run it on my computer and not on Colaboratory.

It does look to be conceptually similar, but your use of a generator and islice rather than a while loop seems to be speed it up a bit.

rabbott
Posts: 1649
Posted 17:15 Feb 05, 2019 |

Here's a colab version of Kevin's code that uses itertools. When run on colab, it averages 0.1 sec, which is consistent with the result that Kevin just reported, i.e., Kevin's computer is faster than colab. (Turns out that mine is slower :( )

Last edited by rabbott at 17:15 Feb 05, 2019.