让我先重写一下你的函数,如
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 所指出的,如果您的阈值足够小且固定,您可以简单地使用模式匹配来确定列表是否足够短。