2

我试图了解 STArray 的工作原理,但我做不到。(Doc很差,或者至少是我找到的那个)。

无论如何,我有下一个算法,但它使用了很多!!,这很慢。如何将其转换为使用 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

编辑

哎呀,!!不是问题;在算法的下一个版本(如下)中,我删除了 !! 的使用;另外,我将 1 固定为素数,正如@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

现在这个问题实际上是关于 2 个问题:

1.- 如何将此算法转换为使用数组而不是列表?(是为了学习如何在 Haskell 中处理状态和数组)有人已经在评论中回答了,但指向一个不太好的解释示例。

2.- 每次找到新素数时如何消除列表的串联?

真 -> primesAcum++[当前]

4

1 回答 1

1

这是您的代码或多或少直接转换为使用未装箱整数数组的方法:

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

这或多或少是不言自明的,当您慢慢阅读时,一次一个陈述。

使用数组,列表连接没有问题,因为没有列表。我们在向其中添加新项目时使用素数数组。

当然,您可以重新编写基于列表的代码以使其表现更好;最简单的重写

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

关键是从递归思维切换到核心递归思维——不是如何在末尾显式地添加,而是定义如何生成列表——让惰性语义处理剩下的事情。

于 2014-10-17T12:29:31.100 回答