【问题标题】:Performance of "all" in haskellhaskell中“all”的表现
【发布时间】:2011-03-17 08:54:06
【问题描述】:

我对 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

这不是说,每个元素都检查条件吗?打破条件的第一个错误结果会不会快得多?这将使上述代码的执行速度更快。

谢谢

【问题讨论】:

    标签: haskell


    【解决方案1】:

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

    【讨论】:

    • 我不认为他的问题是他没有意识到and 短路,而是他认为map 会在and 运行之前遍历整个列表(就像急切语言中的行为一样)因为他不理解/不知道惰性求值。
    【解决方案2】:

    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 为假,评估就会停止。

    此外,还有一条简化规则all/build,它可以有效地将您的all p [1..max] 转换为一个简单的快速故障循环*,所以我认为修改all 不会有太大改进。


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

    eftIntFB c n x0 y | x0 ># y    = n        
                      | otherwise = go x0
                     where
                       go x = I# x `c` if x ==# y then n else go (x +# 1#)
    
    eftIntFB ((&&) . p) True 1# max#
    

    【讨论】:

      【解决方案3】:

      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
      

      【讨论】:

        【解决方案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
        

        【讨论】:

          猜你喜欢
          • 2016-01-23
          • 1970-01-01
          • 2011-04-07
          • 1970-01-01
          • 2013-10-25
          • 1970-01-01
          • 2014-09-27
          • 2021-08-11
          • 1970-01-01
          相关资源
          最近更新 更多