3

I'm learning haskell by going through learnyouahaskell, doing the haskellwiki's 99 problems, and project euler problems as well. I spent most of the day yesterday working on PE problem 3, with no success as all of my solutions would run for long periods of time and bog my computer down. I read through the haskell wiki page on prime numbers but still no success, so I gave up and looked at the solution posted here.

Here is the code:

primes = 2 : filter ((==1) . length . primeFactors) [3,5..]

primeFactors n = factor n primes
    where
        factor n (p:ps) 
            | p*p > n        = [n]
            | n `mod` p == 0 = p : factor (n `div` p) (p:ps)
            | otherwise      = factor n ps

Its fast, it spits out the answer immediately, but I can't wrap my mind around this code. Specifically the primes function and what is happening with the filter, and how this works with primes calling primeFactors and primeFactors calling primes. If I run primes in GHCI it creates an infinite list of prime numbers and I can't figure out why.

Jake Sellers
  • 2,350
  • 2
  • 21
  • 40
  • What did your code look like? – Ry- Aug 22 '14 at 17:07
  • 1
    This code can be improved trivially, too. Testing the length of the list of prime factors isn't lazy enough to skip generating the rest of the factors if it's composite. – Carl Aug 22 '14 at 17:09
  • 1
    Well, it works by checking each new potential prime against the list of known primes, which is less wasteful than checking against every number. `primes` is an infinite list obtained by starting with `2` and continuing by checking the list `[3,5..]` against the currently “available” list. Does that help? – Ry- Aug 22 '14 at 17:12
  • @minitech Ah, I think it just clicked, I guess I didn't understand that it was possible to reference the list inside its own declaration – Jake Sellers Aug 22 '14 at 17:25
  • 2
    A simpler example might be the Fibonacci sequence: `fib = 1 : 1 : zipWith (+) fib (tail fib)`. `fib` can get `1 : 1 :` before it starts to reference items `zipWith` is generating. `tail fib` can get `1 :`. Since it has one from each, it can produce the next item in the sequence, and start using that…. – Ry- Aug 22 '14 at 17:26
  • 4
    @JakeSellers Trivial examples: `ones = 1 : ones` or `naturals = 0 : map (+1) naturals` or `fibs = 0 : 1 : zipWith (+) fibs (tail fibs)`. – Carl Aug 22 '14 at 17:27
  • @minitech ok, I'm still confused on why the filter includes the length ==1 part. – Jake Sellers Aug 22 '14 at 19:22
  • @JakeSellers: Well, it looks like this code was intended to do prime factorization, so the check for the length to be `1` is just a way of reusing it for primality checking. If you just wanted a list of prime numbers, having `primeFactors` turn into `hasFactors` and returning `False` instead of ``p : factor (n `div` p) (p:ps)`` and `True` instead of `[n]` would make sense. – Ry- Aug 22 '14 at 19:25
  • @minitech I think I need to step through this with a debugger because I'm still a bit confused as to whats going on, I appreciate the help though. – Jake Sellers Aug 22 '14 at 20:51
  • 3
    there is also this: http://stackoverflow.com/questions/8469552/why-is-this-haskell-code-snippet-not-infinitely-recursive?rq=1 – meditans Aug 22 '14 at 21:02
  • @Carl is right; that should be `null . tail` or similar instead of `(==1).length`. But there's another inefficiency: `div` and `mod` are rather pricy, and definitely unnecessary here. `quotRem` will improve matters, but better still, I think, would be to implement a proper sieve, and keep track of the largest prime divisor of `n` as you find primes. This would eliminate all the divisions. – dfeuer Aug 23 '14 at 02:24
  • See [Melissa E. O’Neill's paper](http://lambda-the-ultimate.org/node/3127) on implementing the Sieve of Eratosthenes properly in Haskell. – dfeuer Aug 23 '14 at 02:27
  • @dfeuer I found and bookmarked that paper but did not get around to reading it yet, I didn't want to over-complicate the problem for myself. – Jake Sellers Aug 23 '14 at 03:56
  • you could've jumped to http://www.haskell.org/haskellwiki/Prime_numbers#Testing_Primality.2C_and_Integer_Factorization directly. :) – Will Ness Aug 23 '14 at 22:50
  • Maybe use the library [**`Math.NumberTheory.Primes.Factorisation`**](http://hackage.haskell.org/package/arithmoi-0.4.0.1/docs/Math-NumberTheory-Primes-Factorisation.html), just `cabal install arithmoi` – recursion.ninja Aug 24 '14 at 14:54

1 Answers1

2

I'm currently going through the Project Euler questions myself and implemented a very basic solution based on prime sieves and had the same problem as your solution in that it would run for a long time for any number of a medium size or greater

As meditans said in his comment to your question, have a look at this question, particularly the first comment from Will Ness and the accepted answer, both of which helped me a lot.

I also found that these two HaskellWiki pages helped me at the time to understand the solution as well as writing my own functions for the other questions.

Haskell Lazy Evaluation

Performance/Laziness

Community
  • 1
  • 1
Dave0504
  • 1,057
  • 11
  • 30