haskell中“all”的表现

时间:2010-07-17 15:24:27

标签: haskell

我几乎不了解haskell,并试图解决一些Project Euler问题。 在解决Number 5时,我写了这个解决方案(适用于1..10)

--Check if n can be divided by 1..max
canDivAll :: Integer -> Integer -> Bool 
canDivAll max n = all (\x ->  n `mod` x == 0) [1..max]

main = print $ head $ filter (canDivAll 10) [1..]

现在我发现,all是这样实现的:

all p            =  and . map p

这是不是意味着,检查每个元素的条件?打破条件的第一个假结果会不会快得多?这将使上面的代码执行更快。

由于

4 个答案:

答案 0 :(得分:20)

and本身已被短路,因为mapall评估都是懒惰的,您只需要获得所需数量的元素 - 而不是更多。

您可以使用GHCi会话验证:

Prelude Debug.Trace> and [(trace "first" True), (trace "second" True)]
first
second
True
Prelude Debug.Trace> and [(trace "first" False), (trace "second" False)]
first
False

答案 1 :(得分:4)

map执行之前,

and不会评估其所有参数。 and被短路了。

请注意,在GHC中,all并未真正定义为此类。

-- | Applied to a predicate and a list, 'all' determines if all elements
-- of the list satisfy the predicate.
all                     :: (a -> Bool) -> [a] -> Bool
#ifdef USE_REPORT_PRELUDE
all p                   =  and . map p
#else
all _ []        =  True
all p (x:xs)    =  p x && all p xs
{-# RULES
"all/build"     forall p (g::forall b.(a->b->b)->b->b) . 
                all p (build g) = g ((&&) . p) True
 #-}
#endif

我们看到all p (x:xs) = p x && all p xs,因此只要p x为false,评估就会停止。

此外,还有一个简化规则all/build,可以有效地将您的all p [1..max]转换为简单的快速失败循环*,因此我认为您无法通过修改{{1} }。


*。简化的代码应如下所示:

all

答案 2 :(得分:4)

这是一个很好的融合优化程序,因为所有循环都表示为可熔合成器。因此,您可以使用例如Data.Vector,并获得比列表更好的性能。

从N = 20开始,程序中包含列表:

  • 52.484s

另外,请使用rem代替mod

  • 15.712s

列表函数成为向量运算的地方:

import qualified Data.Vector.Unboxed as V

canDivAll :: Int -> Int -> Bool
canDivAll max n = V.all (\x ->  n `rem` x == 0) (V.enumFromN 1 max)

main = print . V.head $ V.filter (canDivAll 20) $ V.unfoldr (\a -> Just (a, a+1)) 1

答案 3 :(得分:1)

您假设and没有短路。 and将停止在它看到的第一个false结果上执行,因此它会像人们预期的那样“快速”。