我一直在解决一个非常简单的问题:生成长度为L
的所有递减序列,由按字典顺序的1
到M
的自然数组成。
但是,我遇到了一个<相当>奇怪的问题。看看:
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)
因此,很明显,这两个函数的作用完全相同(我在多次测试中都对其进行了检查,它们在每个测试上均给出正确的结果),但是如果您尝试评估c 100 98
和{{1} }在GHCi中,您会发现所需的时间存在巨大差异:
As I've mentioned, the result is the same。
因此,每次生成c' 100 98
时我都会感到不安,但我进行了一些询问,有人建议Haskell不会立即进行模式匹配,但会延迟归因于延迟评估,这导致[a..b]
的大量额外调用。但是,第二种理论并不完全成立:我直接在GHCi命令提示符下的代码中设置了一个断点,以监视c'
的值,这表明延迟模式匹配不是这种情况。
问题可能出在n
函数上,还是还有其他原因?
答案 0 :(得分:2)
这两个函数的实现似乎完全不同:
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]
(顺便说一下,为什么要使用不同的名称?以这种方式比较两个代码段比较困难。)因此,我们考虑参数{ {1}}和a
发生变化。参数b
不变,而a
变为b
。
第三个摘要中没有第三个自变量n-1
。
我看不到这将是同一算法。在我看来,这完全不同。您可能在这里引起了更多的递归调用,这使事情变慢了。模式匹配是一个红色的鲱鱼-我认为这并不是在放慢脚步,至少不是直接放慢脚步。
这部分
l
看起来非常可疑。应该是这样的
n <- [a..b]
True <- return $ (l - 1 <= n)
因为上述内容将从 n <- [max a (l-1) .. b]
到a
进行计数,只是丢弃下一行中的那些选择。仅产生选择而放弃选择会降低程序速度。
答案 1 :(得分:1)
将您的True <- return $ (l - 1 <= n)
更改为True <- return $ (l <= n)
,以匹配第一个代码片段所做的事情,这对我来说就等于了两者的时间安排(无需更改答案)。
如果不进行此更改,您的第二个代码段将浪费大量时间,试图在数字l
(对于[1..l-1]
的许多不同值)中找到长度为l
的递减序列任务。