我有以下函数将计数列表转换为离散概率密度函数:
freq2prob l = [ (curr / (sum l))) | curr <- l ]
不幸的是,为每个(sum l)
元素计算了l
,这使得计算复杂度不必要地高。
什么是最简洁,优雅,&#34; haskellic&#34;处理这个的方法?
答案 0 :(得分:4)
很简单:
freq2prob l = [ curr / s | let s = sum l, curr <- l ]
你也可以把它放在列表理解之外:freq2prob l = let s = sum l in [ curr / s | curr <- l ]
(注意in
)。这实际上是相同的计算。
这是因为第一个基本上被翻译成
freq2prob :: (Fractional a) => [a] -> [a]
freq2prob l = [ curr / s | let s = sum l, curr <- l ]
= do
let s = sum l
curr <- l
return (curr / s)
= let s=sum l in
l >>= (\curr -> [curr / s])
-- concatMap (\curr -> [curr / s]) l
-- map (\curr -> curr / s) l
和第二个,显然是相同的代码,
freq2prob l = let s = sum l in [ curr / s | curr <- l ]
= let s = sum l in
do
curr <- l
return (curr / s)
= let s=sum l in
l >>= (\curr -> [curr / s])
答案 1 :(得分:4)
我们可以使用let语句或where子句:
freq2prob l = let s = sum l in
[ curr / s | curr <- l ]
或
freq2prob l = [ curr / s | curr <- l ]
where s = sum l
但使用高阶函数比列表理解更惯用,因为你对每个元素都做了同样的事情:
freq2prob l = map (/sum l) l
分割函数sum l
中的(/sum l)
只会被评估一次。
这是因为在评估map f xs
时,编译器不会产生基本错误,即创建要单独评估的函数f
的多个副本;它是一个thunk,将在每次需要时指向它。
作为一个简单而直率的测试,我们可以研究ghci中的原始时序统计数据,以确定每次重复使用相同的函数或稍微不同的函数是否明显更快。首先,我将检查总和的结果是否通常缓存在ghci中:
ghci> sum [2..10000000]
50000004999999
(8.31 secs, 1533723640 bytes)
ghci> sum [2..10000000]
50000004999999
(8.58 secs, 1816661888 bytes)
所以你可以看到它没有被缓存,并且这些原始统计数据存在一些差异。 现在让我们每次都乘以同样复杂的东西:
ghci> map (* sum [2..10000000]) [1..10]
[50000004999999,100000009999998,150000014999997,200000019999996,250000024999995,300000029999994,350000034999993,400000039999992,450000044999991,500000049999990]
(8.30 secs, 1534499200 bytes)
所以(包括一点变化,使用sum [2..10000000]
乘以map
乘以十个数字几乎完全相同,而不是乘以一个。乘以十对数字几乎不需要任何时间所以ghci(一个解释器,甚至不是优化编译器)都没有引入相同计算的多个副本。
这不是因为ghci很聪明,而是因为懒惰的评估,纯函数式编程的一个很好的特性,从来没有做过多于必要的工作。在大多数编程语言中,很难优化掉在整个地方传递冗长的计算而不是将结果保存在变量中。
现在让我们将其与每次略微不同的计算进行比较,我们在这里计算的数字会略微减少。
ghci> map (\x -> sum [x..10000000]) [1..10]
[50000005000000,50000004999999,50000004999997,50000004999994,50000004999990,50000004999985,50000004999979,50000004999972,50000004999964,50000004999955]
(77.98 secs, 16796207024 bytes)
嗯,正如我们预期的那样,大致大约是的十倍,因为现在我们要求它每次都做不同的事情。我可以验证你为每个数字暂停,而当我们没有更改昂贵的计算数字时,它只被评估一次,暂停在第一个数字之前,其余的快速出现。