我几乎不了解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
这是不是意味着,检查每个元素的条件?打破条件的第一个假结果会不会快得多?这将使上面的代码执行更快。
由于
答案 0 :(得分:20)
and
本身已被短路,因为map
和all
评估都是懒惰的,您只需要获得所需数量的元素 - 而不是更多。
您可以使用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开始,程序中包含列表:
另外,请使用rem
代替mod
。
列表函数成为向量运算的地方:
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
结果上执行,因此它会像人们预期的那样“快速”。