Haskell Mutable Array未修改

时间:2013-11-04 07:06:02

标签: arrays haskell monads

我正在处理Project Euler's问题而我正在14th上。

我有一个可变的IOArray来存储我已经计算过的Collat​​z长度:

import Data.Array.IO
import Control.Monad
import Data.Array

p14 :: IO [Int]
p14 = do
  array <- p14extra
  forM_ [1..1000000] $ \i -> do
    e <- readArray array i
    if e == 0
      then do
        let col  = collatz i
        forM_ col $ \(v,i) -> do
          writeArray array i v
      else return ()
  frozen <- freeze array
  return $ elems frozen

-- an `IOArray` from `1` to `1000000` full of `0`
p14extra :: IO (IOArray Int Int)
p14extra = newArray (1,1000000) 0

collatz :: Int -> [(Int, Int)]
collatz n
  | n == 1    = [(1,1)]
  | otherwise = (n, (snd $ head hack) + 1) : hack
  where
    hack = collatz $ if even n then (n `div` 2) else (3 * n + 1)

其中第一个元素是计算的数字,第二个数字是其Collat​​z序列的长度。

问题在于p14我做writeArray array i v但它总是有一个零(0)数组。那是为什么?

3 个答案:

答案 0 :(得分:4)

我运行了您的确切代码,发现数组已更改。

因为最后有很多零,所以你需要一直滚动到开头才能找到数字。

实际上,如果你替换

  frozen <- freeze array
  return $ elems frozen

  sol <- getElems array
  return (length . takeWhile (/=0) $ sol)

你得到525这是正确的解决方案...更多关于此的结论。

但有几个问题:

  1. 在第forM_ col $ \(v,i) -> do行中,(v,i)应该是(i,v),因为我从中了解到第一个值是索引,第二个值就是值。
  2. 现在我们已经对其进行了整理,如果我们尝试使用forM_ [1..10]newArray (1,10)运行代码,我们会得到*** Exception: Ix{Int}.index: Index (16) out of range ((1,10))。一旦您将值再次设置为1000000,也会发生这种情况,因为在某些情况下,为了计算数字的标记,您必须计算大于1000000的数字的标记。例如,837798的标记包括仅在4个步骤后计算1885048。试图写入该索引会破坏事情。
  3. 对于第二种解决方案,您可以:

    • 尝试制作一个绝对巨大的可变数组,希望你的连环序列永远不会越过界限(我不建议)
    • 尝试制作一个不利用预先计算值的天真版本(主要是为了让它正常工作,然后从那里进行优化)
    • 让它在每次写入之前执行绑定检查
    • ...或者从头开始重新设计,看看你是否能提出更优雅的解决方案;)

    length . takeWhile (/=0) $ sol之所以有效,是因为您使用的是(v,i)而不是(i,v),因此您在索引i <中编写了最小数字,其中包含了一个折叠序列长度i / strong>,所以在索引525你有837799,然后从那里开始它全部为零,因为没有大于525的collat​​z序列。

答案 1 :(得分:1)

我不确定你的意思是“不会保持修改”。

使用此代码(请注意,我已经更改了迭代和数组边界):

import Control.Monad (forM_)
import Data.Array.IO
import Data.Array

p14 :: IO [Int]
p14 = do
  array <- p14extra
  forM_ [1..10] $ \i -> do
    e <- readArray array i
    if e == 0
      then do
        let col  = collatz i
        forM_ col $ \(v,i) -> do
          writeArray array i v
      else return ()
  frozen <- freeze array
  return $ elems frozen

p14extra :: IO (IOArray Int Int)
p14extra = newArray (1,100) 0

collatz :: Int -> [(Int, Int)]
collatz n
  | n == 1    = [(1,1)]
  | otherwise = (n, (snd $ head hack) + 1) : hack
  where
    hack = collatz $ if even n then (n `div` 2) else (3 * n + 1)

p14中运行ghci会产生:

[1,2,4,8,16,5,10,20,40,13,26,52,17,34,11,22,7,14,28,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

答案 2 :(得分:1)

如果这令您感到困惑,那就是一种疯狂的预感:虽然p14修改了它使用的数组,但p14extra 之后将 包含该数组。这是一个IO动作,每次运行时都会创建一个充满零的 new 数组。