如何避免不必要的计算?

时间:2012-09-19 23:38:34

标签: list optimization haskell

如果测试在列表中超过3个元素失败,我需要返回false。有什么我可以做的优化吗?

isItemOk :: Integer -> Boolean 
isItemOk = ( some costly opernation )

这是我想要优化的功能,

isListOk :: [Integer] -> Boolean 
isListOk = 3 >= sum ( [ 1 | x <- [1.1000], isItemOk x ])

我尝试优化,假设如果找到4个元素则不会寻找更多。

isListOk :: [Integer] -> Boolean 
isListOk = 3 >= sum ( take 4 [ 1 | x <- [1.1000], isItemOk x ])

感谢。

4 个答案:

答案 0 :(得分:8)

您可以将filter用于检查失败元素的内容,然后使用take 4并查看length有多少元素。

懒惰评估意味着在找到这四个之后不会检查任何事情,所以你已经完成了。当然,如果三个或更少元素的测试失败,它会检查整个列表,但是你无能为力。

避免的重要之处在于&#34;计算未通过测试的元素&#34;或&#34;过滤然后得到结果的长度&#34;或类似的东西。首先不使用take或类似的东西,这样做会强制检查整个列表。这是&#34;使用null或模式匹配的更一般版本,以检查空列表&#34;经常给初学者的建议。但看起来你已经避免了这个错误!

答案 1 :(得分:6)

对于像3这样的低数字,你可以使用模式匹配。

case filter isItemOk xs of
   x1 : x2 : x3 : _ -> ...
   _                -> ... 

答案 2 :(得分:5)

我想借此机会炒作lazy natural numbers一点。使用这个库和genericLength,我们可以写

import Data.Number.Natural
import Data.List
isListOk = (3 :: Natural) >= genericLength (filter isItemOk [1..1000])

它将不再需要工作:此功能在返回之前最多可找到四个好的项目。例如:

> (3 :: Natural) >= genericLength (filter even (2:4:6:8:undefined))
False

答案 3 :(得分:4)

让我先重写你的功能,如

isListOk :: Bool 
isListOk = length (filter isItemOk [1 .. 1000]) <= 3

可以说比你的版本更惯用。 (请注意,我也更改了类型签名,因为您的类型不正确。此外,您应该写1 .. 1000而不是1.1000。)

懒惰评估是你最好的朋友,因为它通常会确保不会执行不必​​要的计算。

不幸的是,你使用length(或者将列表中的每个元素映射到1然后对结果列表进行求和,就像这样)会妨碍这里。也就是说,length在列表的主干中是严格的:它只能生成列表的长度,如果它将它评估到它的最后,在这种情况下,这意味着你的程序必须运行你的检查一千次。

解决方案是将长度的计算(即列表的脊柱遍历)和测试计算长度是否超过给定阈值的组合合并为一个实际上在懒惰中的单个函数。其参数列表的脊柱:

isNotLongerThan :: [a] -> Integer -> Bool
isNotLongerThan []       n = n >= 0
isNotLongerThan (_ : xs) n = n >= 1 && isNotLongerThan xs (n - 1)

然后写

isListOk :: Bool 
isListOk = filter isItemOk [1 .. 1000] `isNotLongerThan` 3

对于可重用的解决方案,您当然可以在谓词和阈值上进行抽象:

forNoMoreThan :: (a -> Bool) -> Integer -> [a] -> Bool
forNoMoreThan p n = (`isNotLongerThan` n) . filter p

isListOk :: Bool
isListOk = (isItemOk `forNoMoreThan` 3) [1 .. 1000]

最后,正如hammar指出的那样,如果您的阈值足够小并且已修复,您可以简单地使用模式匹配来确定列表是否足够短。