I want to learn using the ST-Monad. Therefore I want to rewrite a bit of code computing for every integer – up to a limit – the list of all its proper divisors. The result should be an array and the entry of index 'n' should be the list of it's proper divisors.
It is done by computing for each Integer 'n' a list 'l' of its multiples and adding for each multiple 'm' out of 'l' at the index 'm' it's divisor 'n' to the list.
Here's the code I want to modify:
properDivisorsOf' :: forall a. (Integral a, Ix a) => a -> Array a [a]
properDivisorsOf' limit =
let generate :: (Integral a, Ix a) => a -> Array a [a] -> Array a [a]
generate n acc
| n > (limit `div` 2) = acc
| otherwise =
let acc' = acc // [(i, n : (acc ! i)) | i <- [2*n, 3*n .. limit]]
in generate (n + 1) acc'
in generate 1 (array (1, limit) [(i, [])| i <- [1..limit]])
And that's the way how I try it:
properDivisorsOf :: forall a. (Integral a, Ix a) => a -> Array a [a]
properDivisorsOf limit =
let result :: ST s (STArray s a [a])
result = newArray (1, limit) [] -- In the beginning for every number: no divisors known (empty list)
update (index, divisor) = do
l <- readArray result index -- extracting list of divisors of number 'index'
let u = divisor : l
writeArray result index u -- and adding 'divisor' to the list
content :: [(a, a)]
content = do
n <- [1 .. (limit `div` 2)]
multiple <- [2*n, 3*n .. limit]
return (multiple, n)
doUpdate = map update content -- update result for all multiples (in content)
in runSTArray result
Unfortunatley it is not compiling and the error message doesn't say anything to me. I have two questions:
- Why doesn't it compile? How can I extract the entry correctly?
- How would an experienced Haskell-Programm solve this problem under the restriction that he has to work with the ST-Monad (for efficiency purposes)
EDIT: Compiler message
Couldn't match expected type `[t0]'
with actual type `STArray i0 a [a]'
In the second argument of `(:)', namely `l'
In the expression: divisor : l
In an equation for `u': u = divisor : l
Failed, modules loaded: none.
Solution
I'm by no means a experienced Haskell programmer, but I would use the
following codethe imperative code below, but here's the direct transition from your code:Comparing non-ST vs ST:
non-ST variant
Your original function:
We exchange
100000
by10000
:Even though the program is pretty fast (1s after all), the memory footprint of 210MB is quite noticeable. Also, the memory needed grows squre, for a limit of 20000 you would need around 800 MB, for 20000 around 1.9GB.
ST variant
Only 38 MB, which is ~17% of your original implementation, but calculates 10 times more values (10000 vs 100000).
Imperative variant
If you throw
content
away, you'll end up with the following program:Why doesn't it compile?
I a sense, you extract correctly (see above, that's almost the same code you used), but the inferred type for
update
is wrong, since your usage isn't correct. For exampleupdate
should work in theST
monad, and therefore you would use it withmapM
, notmap
. Also, there are some other things off, for example you definedoUpdate
but never use it.