Perl, Haskell, stuff
I've been back to Edinburgh for a family reunion and got to spend a morning with my dad who's a maths teacher by trade — I always thought he might be interested in haskell but never got the time to really look at some stuff in a bit of detail. I'd been pointed at Project Euler and thought it would be an interesting thing to look at together.
We worked through 5. What is the smallest number divisible by each of the numbers 1 to 20?
We started off with factorial of 20, and then the product of the primes in the range. Which meant we needed a sieve. I'd written a sieve in Perl (using HOP::Stream
sub pf {
my $s = shift;
my $head = drop $s;
my $s2 = filter { $_[0] % $head } $s;
return node($head, promise { pf($s2) } );
}
I still don't find this technique (wrapping filtering coderefs around each other in a lazy stream) natural, and it took me a while to translate this haskellish technique back into haskell...
> makeSieve n = filter (\i -> i `mod` n /= )
> primes [] = []
> primes (x:xs) = x : (primes $ (makeSieve x) xs)
Which is all very nice, but of course this is not the right number:
> *Main> primes [2..20]
> [2,3,5,7,11,13,17,19]
> *Main> product $ primes [2..20]
> 9699690
The right answer is the product of all the numbers 1..20 except for the ones that are already included in another number. So for example 20*19*18*... but not including 10,5,4, as they're already factors of 20.
We had the idea that we needed to get the factors of each number, so that we could then throw out those whose factors have already been used.
So here's a nice prime factorization technique:
> factorize n = factorize' n (primes [2..]) []
>
> factorize' n (p:px) results
> | n == 1
> = results
> | n `mod` p ==
> = factorize' (n `quot` p) (p:px) (p:results)
> | otherwise
> = factorize' n px results
Used like this
> *Main> factorize 18
> [3,3,2]
But now we want to know the powers of each factor: e.g. in this case, 3^2 and 2^1.
> powf n = map (\l -> (head l, length l) ) $ groupBy (==) n
It made sense for the result to come back as tuples of (value, power):
> *Main> powf $ factorize 18
> [(3,2),(2,1)]
And we can get the whole lot like so:
> *Main> map (powf.factorize) [1..20]
> [[],[(2,1)],[(3,1)],[(2,2)],[(5,1)], ....
But now we just want the maximum power for each one. I tried to get my brain in gear to work out how to do this recursively, then gave up and decided to just sort them...
We sort by value and the descending power. It made sense to use the library function sortBy, which takes a sorting function:
> cmp (a,a') (b,b') | a == b = compare (b') (a')
> | a < b = LT
> | otherwise = GT
(compare is the inbuilt function that compares two Ord values)
> *Main> sortBy cmp $ concatMap (powf.factorize) [1..20]
Now we want the top power for each value. The way that came to mind was to group by the value:
> groupBy (\(x,_) (y,_) -> x == y) $
> sortBy cmp $ concatMap (powf.factorize) [1..20]
Now that we've grouped we just want the maximum ord for each:
> map (\l -> head l) $
> groupBy (\(x,_) (y,_) -> x == y) $
> sortBy cmp $ concatMap (powf.factorize) [1..20]
Giving us (value,power) pairs like so:
> [(2,4),(3,2),(5,1),(7,1),(11,1),(13,1),(17,1),(19,1)]
And then of course we want to exponentiate these value:
> let factors = map (\(n,p) -> n ^ p ) $
> map (\l -> head l) $
> groupBy (\(x,_) (y,_) -> x == y) $
> sortBy cmp $ concatMap (powf.factorize) [1..20]
Now it's just a matter of
> *Main> product factors
And Bob's your uncle. What could be simpler? ;-)
I clicked through to the Project Euler discussion forums to see a nice Ruby version:
num = (1..20).inject(1) { |result, n| result.lcm n }
OK, Ruby still seems weird to me, but that's because I haven't learnt it. The point is that this seems rather more compact. As soon as I pointed this to my Dad, he said, “Of course it's a least common multiple problem”. Which of course is more or less the approach that we'd taken, but rather more verbosely than the Ruby version.
So I googled “haskell lcm” to check if there was a function with a similar name somewhere. There was indeed a function lcm... in the Prelude itself.
“Oh,” I said, “maybe this will work...” and typed the following into ghci:
> foldl1 lcm [1..20]
This of course is equivalent to the above... but 20 times shorter. This is both humbling and rather interesting. I think it was worth going through the entire process to get to this point, as I learnt a lot. Also, now having an (inefficient) prime sieve and a prime factoriser, I can easily solve a number of other questions:
3: Find the largest prime factor of 317584931803.
> *Main> factorize 317584931803
7: Find the 10001st prime number
> *Main> show $ last (take 10001 $ primes [2..])
(This one takes quite a long time - about 12 seconds - to complete. Apparently there are much better sieves, but we're still comfortably within the project's “minute rule” so I'm not going to prematurely optimize yet!
Update: bluestorm points out that what I believed to be a Sieve of Eratosthenes actually isn't, citing The Genuine Sieve of Eratosthenes, a very readable paper on the topic.
Osfameron's blog on Haskell, Perl programming, stuff.
Leave a reply