【问题标题】:How could I optimize this list comprehension?我怎样才能优化这个列表理解?
【发布时间】:2012-02-02 13:42:27
【问题描述】:

我想从一个列表中生成所有可能的对,限制为 (a,b) == (b,a) 和 ((a,b),(c,d)) = = ((c,d),(a,b)) 对于所有 a、b、c、d。此外,我可以假设我作为参数提供的列表中的所有元素都是不同的。

我做的第一件事就是写下这个列表理解:

pairsOfPairs :: [a] -> [((a,a),(a,a))]
pairsOfPairs xs = [((w,x),(y,z)) | w <- xs, x <- xs, y <- xs, z <- xs,
                      w < x, y < z, w < y, w /= z, x /= y, x /= z]

这具有惯用的优点,但速度很慢(分析表明,接近 90% 的运行时间都花在了这个函数和另一个类似的函数上)。

缓慢的原因是,对于一个包含 n 个元素的列表,它会生成 n^4 对候选对,但限制最终将其缩减为 n!/(8 * (n-4)!),这意味着我们至少做了 8 倍的工作量。

有没有办法重写函数pairsOfPairs 来提高速度?显然它仍然是 O(n^4),但我希望降低常数。


编辑:事实上,我几乎总是用长度为 5 的列表来调用这个函数,这意味着结果中有 5!/8 = 15 个元素,但是该函数会生成一个5^4 = 625 个元素作为中间步骤。如果我能消除所有这些中间元素,我将因此获得大约 40 倍的加速!

【问题讨论】:

  • 您的第一段似乎暗示a = b = c = d。我不认为这就是你的意思。
  • @dave4420 我使用 '==' 来表示等价,即 (a,b) 对应被视为与 (b,a) 对等价,但不是 a 和b 必须相等。

标签: optimization haskell profiling


【解决方案1】:

减少工作量的简单方法是尽早过滤。

pairsOfPairs :: Ord a => [a] -> [((a,a),(a,a))]
pairsOfPairs xs = [((w,x),(y,z)) | w <- xs, x <- xs, w < x, y <- xs, w < y, x /= y, 
                                   z <- xs, y < z, w /= z, x /= z]

通过在条件可用时立即检查条件,我们避免了 (w,x)x &lt;= w 等每对的 O(n^2) 工作。这还不错。

但是通过预处理列表可以获得更多,如果它是排序的,我们可以通过选择像这样的对来避免几乎所有不必要的工作

ordPairs :: [a] -> [(a,a)]
ordPairs (x:xs) = [(x,y) | y <- xs] ++ ordPairs xs
ordPairs [] = []

【讨论】:

  • 对列表理解中的绑定和过滤器进行简单的重新排序,在速度上有很大的不同 - 非常感谢!出于好奇,GHC 自己不能优化这个是有原因的吗?是因为列表推导是 do 表示法的糖,而 do 块是必不可少的吗?
  • 您可能还喜欢来自this answer的实现:pairs xs = [(x, y) | x:ys &lt;- tails xs, y &lt;- ys]
  • 非常好,@Daniel,我喜欢这样。
  • @Chris do 块是(&gt;&gt;=)(&gt;&gt;) 链的语法糖,它们不是必须的。它们往往看起来势在必行,有时这会使代码更清晰,有时则不然。 GHC 本身不能进行这种转换的唯一原因是它没有实施。我倾向于认为它没有实现,因为很难验证重新排序不会改变语义并提高性能,而且 GHC 黑客根本不够,所以到目前为止没有人有时间这样做。
猜你喜欢
  • 1970-01-01
  • 2021-04-30
  • 1970-01-01
  • 1970-01-01
  • 2016-06-13
  • 1970-01-01
  • 1970-01-01
  • 2019-06-13
  • 2022-01-11
相关资源
最近更新 更多