【问题标题】:Multiple updates in ST-MonadST-Monad 中的多次更新
【发布时间】:2014-02-14 12:43:33
【问题描述】:

我想学习使用 ST-Monad。因此,我想为每个整数重写一些代码计算——直到一个限制——所有正确除数的列表。结果应该是一个数组,索引“n”的条目应该是它的正确除数列表。

这是通过为每个整数“n”计算其倍数的列表“l”并在索引“m”处将“l”中的每个倍数“m”添加到列表中的除数“n”来完成的。

这是我要修改的代码:

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]])

我就是这样尝试的:

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)

在 runSTArray 结果中

不幸的是,它没有编译,并且错误消息对我没有任何意义。我有两个问题:

  1. 为什么不能编译?如何正确提取条目?
  2. 一个有经验的 Haskell 程序如何在他必须使用 ST-Monad 的限制下解决这个问题(为了提高效率)

编辑:编译器消息

    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

失败,已加载模块:无。

【问题讨论】:

  • 如果您想知道编译器在抱怨什么,向我们展示错误消息可能会有所帮助。

标签: haskell starray


【解决方案1】:

解决方案

2。一个有经验的 Haskell 程序如何在他必须使用 ST-Monad 的限制下解决这个问题(为了提高效率)?

我绝不是经验丰富的 Haskell 程序员,但我会使用 以下代码 下面的命令式代码,但这里是您的代码的直接转换:

properDivisorsOf :: forall a. (Integral a, Ix a) => a -> Array a [a]
properDivisorsOf limit =
  runSTArray $ do
    result <- newArray (1, limit) []
    mapM_ (update result) content
    return result
  where
      content :: [(a, a)]
      content = do
        n <- [1 .. (limit `div` 2)]
        multiple <- [2*n, 3*n .. limit]
        return (multiple, n)

      update arr (index, divisor) = do
        l <- readArray arr index -- extracting list of divisors of number 'index'
        let u = divisor : l
        writeArray arr index u   -- and adding 'divisor' to the list

比较非 ST 与 ST:

非 ST 变体

你原来的函数:

main = print $ properDivisorsOf' 100000
$ .\Original.exe +RTS -s
Original.exe:内存不足

我们通过10000交换100000

在堆中分配了 221,476,488 字节 GC 期间复制了 21,566,328 个字节 172,813,068 字节最大驻留(9 个样本) 4,434,480 字节最大斜率 210 MB 总内存正在使用(5 MB 由于碎片丢失) 总时间(经过) 平均暂停 最大暂停 Gen 0 378 colls,0 par 0.41s 0.43s 0.0011s 0.0024s Gen 1 9 colls,0 par 0.36s 0.37s 0.0409s 0.1723s 初始化时间 0.00s(经过 0.00s) MUT时间0.28s(经过0.60s) GC时间0.77s(经过0.80s) 退出时间 0.00s(经过 0.02s) 总时间1.05s(经过1.42s) %GC 时间 73.1%(经过 56.4%) 分配速率 787,471,957 字节/MUT 秒 生产力占总用户的 26.9%,占总使用时间的 19.8%

尽管程序非常快(毕竟是 1 秒),但 210MB 的内存占用还是很明显的。此外,所需的内存会增加,对于 20000 的限制,您需要大约 800 MB,对于 20000 大约需要 1.9GB。

ST 变体

$ .\ST.exe +RTS -s > $null
     在堆中分配了 300,728,400 字节
      GC 期间复制了 89,696,288 字节
      13,628,272 字节最大驻留(10 个样本)
         292,972 字节最大斜率
              38 MB 总内存正在使用(0 MB 由于碎片丢失)

                                    总时间(经过) 平均暂停 最大暂停
  Gen 0 565 colls,0 par 0.14s 0.14s 0.0002s 0.0008s
  Gen 1 10 colls,0 par 0.09s 0.08s 0.0076s 0.0223s

  初始化时间 0.00s(经过 0.00s)
  MUT 时间 0.11s(经过 0.16s)
  GC时间0.23s(经过0.21s)
  退出时间 0.00s(经过 0.00s)
  总时间 0.34s(经过 0.38s)

  %GC 时间 68.2%(经过 56.6%)

  分配速率 2,749,516,800 字节/MUT 秒

  生产力占总用户的 31.8%,占总使用时间的 28.9%

只有 38 MB,大约是原始实现的 17%,但计算的值多 10 倍(10000 与 100000)。

命令式变体

如果你把content 扔掉,你会得到以下程序:

import Data.Array (Array(..), Ix)
import Data.Array.ST (newArray, readArray, writeArray, runSTArray)
import Control.Monad (forM_)

properDivisorsOf :: (Integral a, Ix a) => a -> Array a [a]
properDivisorsOf limit =
  runSTArray $ do
    result <- newArray (1, limit) []
    forM_ [1.. (limit `div`2)] $ \n -> do
      forM_ [2*n, 3*n .. limit] $ \index -> do
        l <- readArray result index
        writeArray result index (n:l)
    return result

main = print $ properDivisorsOf 100000
$ .\Imperative.exe +RTS -s > $null
     在堆中分配了 237,323,392 个字节
      GC 期间复制了 63,304,856 个字节
      13,628,276 字节最大驻留(7 个样本)
         138,636 字节最大斜率
              34 MB 总内存正在使用(0 MB 由于碎片丢失)

                                    总时间(经过) 平均暂停 最大暂停
  Gen 0 447 colls,0 par 0.12s 0.09s 0.0002s 0.0008s
  Gen 1 7 colls,0 par 0.05s 0.06s 0.0087s 0.0224s

  初始化时间 0.00s(经过 0.00s)
  MUT时间0.11s(经过0.18s)
  GC时间0.17s(经过0.16s)
  退出时间 0.00s(经过 0.00s)
  总时间 0.30s(经过 0.34s)

  %GC 时间 57.9%(经过 45.9%)

  分配速率 2,169,813,869 字节/MUT 秒

  生产力占总用户的 42.1%,占总用户的 36.9%

为什么不编译?

  1. 为什么不能编译?如何正确提取条目?

我的意思是,您提取正确(见上文,这与您使用的代码几乎相同),但update 的推断类型是错误的,因为您的用法不正确。例如update 应该在ST monad 中工作,因此您可以将它与mapM 一起使用,而不是map。此外,还有一些其他的事情,例如您定义了doUpdate,但从不使用它。

【讨论】:

  • 注意:这是I'm using Array 的第二次,也是我第一次使用ST,如果它关闭,请随意核对这个答案。
  • 一个编码提示:由于你没有使用mapM的结果,考虑使用mapM_。还可以查看 forMforM_ 进行单子迭代。
  • 谢谢 Zeta 和 user5402。
  • 我什么都试过了,效果很好。所以我想,如果我想像以前一样定义更新函数(不给数组作为参数),我必须在使用 let-construct 之后定义它。这似乎是明智的。我将尝试更好地理解 ST 中使用的“forall”类型结构的概念。但如果我没看错的话,尤其是因为我最初没有用“
  • @user2292040:这基本上是冰山一角,是的。在我的例子中,resultSTArray,而你仍然把它包裹在 ST monad 中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-03
  • 1970-01-01
  • 2013-07-18
  • 1970-01-01
  • 1970-01-01
  • 2019-01-23
  • 2022-01-22
相关资源
最近更新 更多