繁体   English   中英

可以在do-notation / enumFrom中进行模式匹配以减慢Haskell代码的速度吗?

[英]Could pattern-matching in do-notation/enumFromTo slow down a Haskell code?

我一直在解决一个很简单的问题:生成长度为L的所有递减序列,按字典顺序从1M的自然数组成。 但是,我遇到了一个非常奇怪的问题。 看一看:

c :: (Ord a, Num a, Enum a) => a -> a -> [[a]]
c m 1 = map return [1..m]
c m l = do
          n   <- [l..m]
          res <- c (n - 1) (l - 1)
          return $ n:res

c' :: (Ord a, Num a, Enum a) => a -> a -> [[a]]
c' m = helper 1 m where
 helper :: (Ord a, Num a, Enum a) => a -> a -> a -> [[a]]
 helper a b 1 = map return [a..b]
 helper a b l = do
                  n    <- [a..b]
                  True <- return $ (l - 1 <= n)
                  res  <- helper a (n - 1) (l - 1)
                  return (n:res)

因此,很显然,这两个函数的作用完全相同(我在许多测试中都对其进行了检查,它们在每个测试上均给出了正确的结果),但是如果您尝试在GHCi中评估c 100 98和c'100 c' 100 98 ,您将看到花费时间的巨大差异:

c 100 98:大约5秒钟

c'100 98:大约70秒

正如我已经提到的,结果是相同的

因此,我对每次生成[a..b]都感到不安,但我进行了一些询问,有人建议Haskell不会立即进行模式匹配,但会延迟到懒惰求值,这会导致大量额外的c'调用。 但是,第二种理论还不太成立:我直接在GHCi命令提示符下的代码中设置了一个断点,以监视n的值,这表明延迟模式匹配不是这种情况。

问题可能出在enumFromTo函数上,还是还有其他原因?

这两个函数似乎具有完全不同的实现:

c m l = do
      n   <- [l..m]
      res <- c (n - 1) (l - 1)
      return $ n:res

此处,在每个递归调用中,参数l都会递减,而参数m会变为n <- [l--m]

通过对比,

helper a b l = do
    n    <- [a..b]
    True <- return $ (l - 1 <= n)
    res  <- helper a (n - 1) (l - 1)
    return (n:res)

这里的间隔是[a..b]而不是[l..m] (顺便说一下,为什么要使用不同的名称?以这种方式比较两个代码段比较困难。)因此,我们考虑参数ab改变。 参数a不变,而b变为n-1

第一个代码段中没有第三个参数l

我看不到这将是相同的算法。 在我看来,这完全不同。 您可能在这里引起了更多的递归调用,这使事情变慢了。 模式匹配是一个红色的鲱鱼-我认为这并不是在放慢脚步,至少不是直接放慢脚步。

另外,这部分

    n    <- [a..b]
    True <- return $ (l - 1 <= n)

看起来很可疑。 应该是这样的

    n    <- [max a (l-1) .. b]

因为上述内容将从al-2计数,只是舍弃了下一行中的选择。 仅产生选择而放弃选择会降低程序速度。

将您的True <- return $ (l - 1 <= n)更改为True <- return $ (l <= n) ,以匹配第一个代码片段所做的事情,对我来说等于两个时间(不更改答案) 。

如果不进行此更改,您的第二个代码段将浪费大量时间,试图在数字[1..l-1] (对于l许多不同值)中找到长度为l递减序列,这注定了任务的完成。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM