Haskell 如何在恒定内存中获取make stats
我有一个函数,它创建了一些随机的数值结果。我知道,结果将是a(小,a-b约50)范围a,b的整数。我想创建一个函数,执行上面的函数,比如1000000次,并计算每个结果出现的频率。(该函数需要一个随机生成器来生成结果。)问题是,我不知道如何在恒定内存中进行此操作,而不硬编码范围的长度。我的(坏)方法是这样的:Haskell 如何在恒定内存中获取make stats,haskell,random,statistics,memory-management,lazy-evaluation,Haskell,Random,Statistics,Memory Management,Lazy Evaluation,我有一个函数,它创建了一些随机的数值结果。我知道,结果将是a(小,a-b约50)范围a,b的整数。我想创建一个函数,执行上面的函数,比如1000000次,并计算每个结果出现的频率。(该函数需要一个随机生成器来生成结果。)问题是,我不知道如何在恒定内存中进行此操作,而不硬编码范围的长度。我的(坏)方法是这样的: values :: [Int] values = doFunctionNtimes myRandom 1000000 results = map (\x ->length . fil
values :: [Int]
values = doFunctionNtimes myRandom 1000000
results = map (\x ->length . filter (x==) $ values) [a..b]
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
foldr iw {} [1,2,1,3,2,1]
iw 1 (foldr iw {} [2,1,3,2,1])
iw 1 (iw 2 (foldr iw {} [1,3,2,1]))
iw 1 (iw 2 (iw 1 (foldr iw {} [3,2,1])))
iw 1 (iw 2 (iw 1 (iw 3 (foldr iw {} [2,1]))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 (foldr iw {} [1])))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 (iw 1 (foldr iw {} []))))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 (iw 1 {}))))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 {1 -> 1}))))
iw 1 (iw 2 (iw 1 (iw 3 {1 -> 1, 2 -> 1})))
iw 1 (iw 2 (iw 1 {1 -> 1, 2 -> 1, 3 -> 1}))
iw 1 (iw 2 {1 -> 2, 2 -> 1, 3 -> 1})
iw 1 {1 -> 2, 2 -> 2, 3 -> 1}
{1 -> 3, 2 -> 2, 3 -> 1}
有人有这样的想法吗
编辑:
我想我把问题解释错了,很抱歉。我有一个函数,它根据一个随机的gen,给出一些小的int值。为了进行统计,我想知道结果出现的频率。当我想统计1000000次尝试时,我需要对尝试次数保持恒定的内存。因此,您有无限数量的可能结果,并想计算每个结果在恒定内存中出现的次数。这显然是不可能做到的,但是调用的数据结构可以用来做一个非常好的近似。在您的情况下,将结果存储在count min草图中,同时分别跟踪最小值和最大值,并在最后查询count min草图中从最小值到最大值的每个整数。正如Jouni已经提到的,恒定内存是不可能的,但这个count min草图听起来像炸弹!(虽然我以前没听说过)。但我想你可能要求的是将其存储在一个数组中,并且只更新每个频率。这可以在haskell中使用可变数组来完成。以下是一个例子:
main = do gen <- newStdGen
n <- liftM (read . head) getArgs
arr <- (newArray (a,b) 0) :: IO (IOUArray Int Int)
replicateM_ n $ do
result <- myRand
x <- readArray arr result
writeArray arr result (x+1)
(getAssocs arr :: IO [(Int,Int)]) >>= print
我通常处理这类问题的方法是跟踪地图中的计数<代码>数据。IntMap在这种情况下起作用:
import qualified Data.IntMap as I
results :: [Int] -> I.IntMap Int
results = foldr (\x -> I.insertWith (+) x 1) I.empty
此时,您可以询问范围的端点(I.findMin
和I.findMax
),或在O(log n)中查找特定值的计数。为了更快地查找,将所有内容粘贴在一个数组中也非常容易
更新:有关此代码的更好版本,请参阅
import qualified Data.Map as Map
import Data.List (foldl') -- ' (to fix SO syntax highlighting)
histogram :: (Ord a) => [a] -> Map.Map a Int
histogram = foldl' (\m x -> Map.insertWith' (+) x 1 m) Map.empty
关于为什么这一方法有效以及为什么它优于Travis Brown的解决方案的解释是相当技术性的,需要一些耐心才能完全理解
如果列表中可能出现的值只有有限多个,那么它将在恒定内存中运行。Travis的解决方案有一个微妙的缺陷,其中生成的地图条目如下所示:
(4, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1)
数字19的一种非常低效的表示。只有当你在地图中要求那个元素时,才会计算出这个巨大的总和。这些“thunks”(延迟计算表达式)将随着输入的大小线性增长
为了防止出现这种情况,我们使用了insertWith'
,它严格应用函数,也就是说它在将结果放入映射之前对结果进行评估。因此,如果您在上面的地图中插入4,它将评估thunk,您将得到一个漂亮的整洁:
(4, 20)
另一个将在添加之前进行评估,因此您将获得:
(4, 21)
所以现在至少地图的值是常量空间
我们需要做的最后一件事是将右折叠更改为左折叠,因为Map.insert在其第二个参数中是严格的。下面演示了右折叠的含义
iw x m = Map.insertWith' (+) x 1 m -- '
foldr iw Map.empty [1,2,1,3,2,1]
= iw 1 (iw 2 (iw 1 (iw 3 (iw 2 (iw 1 Map.empty)))))
使用iw
作为简单的速记Map.insert
在第二个参数中严格要求,这意味着您需要评估要插入的映射,然后insert才能执行任何操作。我将使用符号{k1->v1,k2->v2,…}
作为地图的简写。您的评估顺序如下所示:
values :: [Int]
values = doFunctionNtimes myRandom 1000000
results = map (\x ->length . filter (x==) $ values) [a..b]
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
foldr iw {} [1,2,1,3,2,1]
iw 1 (foldr iw {} [2,1,3,2,1])
iw 1 (iw 2 (foldr iw {} [1,3,2,1]))
iw 1 (iw 2 (iw 1 (foldr iw {} [3,2,1])))
iw 1 (iw 2 (iw 1 (iw 3 (foldr iw {} [2,1]))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 (foldr iw {} [1])))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 (iw 1 (foldr iw {} []))))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 (iw 1 {}))))))
iw 1 (iw 2 (iw 1 (iw 3 (iw 2 {1 -> 1}))))
iw 1 (iw 2 (iw 1 (iw 3 {1 -> 1, 2 -> 1})))
iw 1 (iw 2 (iw 1 {1 -> 1, 2 -> 1, 3 -> 1}))
iw 1 (iw 2 {1 -> 2, 2 -> 1, 3 -> 1})
iw 1 {1 -> 2, 2 -> 2, 3 -> 1}
{1 -> 3, 2 -> 2, 3 -> 1}
所以如果你有一个1000000个元素的数组,我们必须一直到第1000000个元素才能开始插入,因此我们需要将之前的999999个元素保留在内存中,这样我们就可以知道接下来要做什么。左折叠解决了这个问题:
-- definition of left fold
foldl' f z xs = go z xs -- '
where
go accum [] = z
go accum (x:xs) = accum `seq` go (f accum x) xs
foldl' (flip iw) Map.empty [1,2,1,3,2,1] -- needed to flip arg order to appease foldl'
go {} [1,2,1,3,2,1]
go (iw 1 {}) [2,1,3,2,1]
go (iw 2 {1 -> 1}) [1,3,2,1]
go (iw 1 {1 -> 1, 2 -> 1}) [3,2,1]
go (iw 3 {1 -> 2, 2 -> 1}) [2,1]
go (iw 2 {1 -> 2, 2 -> 1, 3 -> 1}) [1]
go (iw 1 {1 -> 2, 2 -> 2, 3 -> 1}) []
iw 1 {1 -> 2, 2 -> 2, 3 -> 1}
{1 -> 3, 2 -> 2, 3 -> 1}
现在我们可以看到,最后,如果映射中的条目数是有界的,那么它在恒定的空间和线性时间中运行。myRandom实际上是RNG吗?如果是这样的话,
值是如何类型为[Int]
而不是IO[Int]
、MonadRandom r=>r[Int]
或类似的东西的?它看起来不需要任何参数,所以除非有类似的情况,否则它应该只返回重复1000000次的相同值的列表。在这种情况下,您的范围a,b
由Int
的大小限制,这取决于实现,但通常是32位的。Haskell报告说Int应该不小于31位,并且至少有一个编译器使用这个最小值。myRandom只是一个函数,它生活在一个状态RandomGen
monad中,我不想让描述变得更复杂。@John:是的,O(2**32)是常量,但在类似意义上,你的计算机是一个有限状态机。通常,当人们谈论“常量内存”时,他们希望常量相当小。@Jouni,我大体上同意,除了Haskeller经常混淆Int
和Integer
,并期望前者具有无限精度之外。这就是我提到它的原因。Haskell通常不喜欢类似哈希表的方法,因为它们强烈依赖于杂质。因此,您将失去可组合性,这是Haskell最大的优势之一。通常,您可以根据树或哈希和树的组合来重新构造此类方法。但是CM的作者并没有考虑到杂质的问题,这是一项相当新的工作,所以我怀疑还有其他人做过这件事。您可以在Haskell中使用不纯方法实现它,但在这种情况下,您也可以使用FFI链接到C库。有人使用持久向量实现了Bloom过滤器,CM草图是该方法的推广,因此您可能会做类似的事情:它不会是恒定内存。我很难过,整数的范围为a、b,其中