2

I tried to learn how the STArray works, but I couldn't. (Doc is poor, or at least the one I found).

Any way, I have the next algorithm, but it uses a lot of !!, which is slow. How can I convert it to use the STArray monad?

-- The Algorithm prints the primes present in [1 .. n]

main :: IO ()
main = print $ primesUpTo 100

type Nat = Int

primesUpTo :: Nat -> [Nat]
primesUpTo n = primesUpToAux n 2 [1]

primesUpToAux :: Nat -> Nat -> [Nat] -> [Nat]
primesUpToAux n current primes = 
  if current > n
  then primes
  else primesUpToAux n (current + 1) newAcum
  where newAcum = case isPrime current primes of
                  True  -> primes++[current]
                  False -> primes

isPrime :: Nat -> [Nat] -> Bool
isPrime 1 _ = True
isPrime 2 _ = True
isPrime x neededPrimes = isPrimeAux x neededPrimes 1

isPrimeAux x neededPrimes currentPrimeIndex = 
  if sqrtOfX < currentPrime
  then True
  else if mod x currentPrime == 0
       then False
       else isPrimeAux x neededPrimes (currentPrimeIndex + 1)
  where
        sqrtOfX = sqrtNat x
        currentPrime = neededPrimes !! currentPrimeIndex

sqrtNat :: Nat -> Nat
sqrtNat = floor . sqrt . fromIntegral

Edit

Oops, the !! wasn't the problem; in the next version of the algorithm (below) I've removed the use of !!; also, I fixed 1 being a prime, which is not, as pointed by @pedrorodrigues

main :: IO ()
main = print $ primesUpTo 20000

type Nat = Int

primesUpTo :: Nat -> [Nat]
primesUpTo n = primesUpToAux n 1 []

primesUpToAux :: Nat -> Nat -> [Nat] -> [Nat]
primesUpToAux n current primesAcum = 
    if current > n
    then primesAcum
    else primesUpToAux n (current + 1) newPrimesAcum
    where newPrimesAcum = case isPrime current primesAcum of
                          True  -> primesAcum++[current]
                          False -> primesAcum

isPrime :: Nat -> [Nat] -> Bool
isPrime 1 _ = False
isPrime 2 _ = True
isPrime x neededPrimes =
    if sqrtOfX < currentPrime
    then True
    else if mod x currentPrime == 0
         then False
         else isPrime x restOfPrimes
    where
          sqrtOfX = sqrtNat x
          currentPrime:restOfPrimes = neededPrimes

sqrtNat :: Nat -> Nat
sqrtNat = floor . sqrt . fromIntegral

Now this question is about 2 questions really:

1.- How to transform this algorithm to use arrays instead of lists? (Is for the sake of learning how to handle state and arrays in Haskell) Which somebody already answered in the comments, but pointing to a not very good explained example.

2.- How to eliminate the concatenation of lists every time a new prime is found?

True -> primesAcum++[current]

Lay González
  • 2,901
  • 21
  • 41
  • 4
    STArray isn't needed here, the algorithm's just trial divison. You should look at the standard list manipulation functions (`filter`, `takeWhile`, `all`, `any`, etc) before anything else, I think, since the problem can be optimally and idiomatically solved using them, and here you're making quite the detours. – András Kovács Oct 15 '14 at 07:43
  • 3
    You want a fast implementation in a functional style? then see previous comment (and make sure to avoid the fallacy of blindly representing sets by lists). Or: you want to learn to use STArray - then describe exactly what is your problem with STArray. You realize you have to write "imperative" code then (everything's in the ST monad). – d8d0d65b3f7cf42 Oct 15 '14 at 07:51
  • 3
    1 is not a prime, by the way. – Pedro Rodrigues Oct 15 '14 at 10:58
  • 2
    Haskell wiki has an example of calculating prime numbers using ST Arrays: http://www.haskell.org/haskellwiki/Prime_numbers#Using_ST_Array – Pedro Rodrigues Oct 15 '14 at 11:11
  • @PedroRodrigues I fixed the algorithm, 1 is no longer considered a prime. I looked at that example from the haskell wiki, but It really is not well commented nor explained. – Lay González Oct 15 '14 at 16:15
  • @d8d0d65b3f7cf42 I do want to learn how to use STArray. My problem is how can I fill the STArray with a recursive function? – Lay González Oct 15 '14 at 16:59
  • 1
    To address your second question: instead of doing `primesAcum++[current]`, which is O(n^2), do `current:primesAccum` and reverse the list at the very end, which is O(n). – user2407038 Oct 15 '14 at 20:02
  • I'm surprised nobody has mentioned that STArray is usually not used in favor of MVectors – alternative Oct 18 '14 at 02:27
  • 1
    You might want to consider using a better algorithm: the [Sieve of Eratosthenes](http://lambda-the-ultimate.org/node/3127). – dfeuer Oct 18 '14 at 16:08

1 Answers1

1

Here's a more or less direct translation of your code into working with an unboxed array of integers:

import Control.Monad
import Control.Monad.ST
import Data.Array.ST
import Data.Array.Unboxed
import Control.Arrow

main :: IO ()
main = print . (length &&& last) . primesUpTo $ 1299709

primesUpTo :: Int -> [Int]
primesUpTo = takeWhile (> 0) . elems . primesUpToUA 

primesUpToUA :: Int -> UArray Int Int
primesUpToUA n = runSTUArray $ do
  let k = floor( 1.25506 * fromIntegral n / log (fromIntegral n)) -- WP formula
  ar <- newArray (0,k+1) 0            -- all zeroes initially, two extra spaces 
  let 
    loop c i | c > n = return ar           -- upper limit is reached 
             | otherwise = do              -- `i` primes currently in the array
         b <- isPrime 0 i c                -- is `c` a prime?
         if  b  then do { writeArray ar i c ; loop (c+1) (i+1) }
         else   loop (c+1) i
    isPrime j i c | j >= i = return True   -- `i` primes 
                  | otherwise = do         --   currently in the array 
            p <- readArray ar j
            if   p*p > c           then return True 
            else  if rem c p == 0  then return False 
            else                   isPrime (j+1) i c
  loop 2 0

This is more or less self-explanatory, when you read it slowly, one statement at a time.

Using arrays, there's no problems with list concatenation, as there are no lists. We use the array of primes as we're adding new items to it.

Of course you can re-write your list-based code to behave better; the simplest re-write is

ps :: [Int]
ps = 2 : [i | i <- [3..],  
              and [rem i p > 0 | p <- takeWhile ((<=i).(^2)) ps]]
primesTo n = takeWhile (<= n) ps

The key is to switch from recursive thinking to corecursive thinking - not how to add at the end, explicitly, but to define how a list is to be produced — and let the lazy semantics take care of the rest.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Thanks a lot. One question, I understand that newArray (0,k+1) 0 produces an ST s (STUArray s Int Int), right? What is the type of s (the state)? I couldn't figured out neither the type of isPrime. – Lay González Oct 18 '14 at 00:37
  • to see the type of any entity, e.g. `isPrime`, add `let x = isPrime == True` and read the error message about the type mismatch between `Bool` - and the type you want to know. -- we know `runSTUArray :: Ix i => (forall s. ST s (STUArray s i e)) -> UArray i e`, so the `do` expression in it is `:: Ix i => (forall s. ST s (STUArray s i e))`. ["A computation of type ST s a transforms an internal state indexed by s, and returns a value of type a"](http://www.haskell.org/ghc/docs/7.6.2/html/libraries/base-4.6.0.1/Control-Monad-ST-Safe.html#t:ST). (...cont) – Will Ness Oct 18 '14 at 09:53
  • (...contd) so the calculations in that do expression are *in* the `ST s` monad. `s` is encapsulated by the `forall s.` so we don't know what it is, and must'nt know. [`newArray :: Ix i => (i, i) -> e -> m (a i e)`](http://www.haskell.org/ghc/docs/7.6.2/html/libraries/array-0.4.0.1/Data-Array-MArray.html#t:MArray) so `m ~ ST s` with the invisible `s`, and `arr :: a i e ~ STUArray s i e` (because we *pull* it *from* monadic computation); `e` is `Int` and `i` is in [`Ix` - the ranged index](http://www.haskell.org/ghc/docs/7.6.2/html/libraries/base-4.6.0.1/Data-Ix.html#t:Ix), like (Int, Int). – Will Ness Oct 18 '14 at 10:00
  • ... `arr :: a i e ~ STUArray s i e` with the *same* invisible `s`. -- `newArray (0,k+1) 0 :: ST s (STUArray s Int Int)` and we "pull/get" `arr` from it. – Will Ness Oct 18 '14 at 10:05
  • so yes, you got it right. and `isPrime` should be `Int -> Int -> Int -> ST s Bool` (again with the same invisible `s`). – Will Ness Oct 18 '14 at 10:11