合格 Haskell 列表理解的有效替代方案

Efficient alternatives to qualified Haskell list comprehensions

作为 Haskell 中合格列表推导的说明,Learn You a Haskell 教程提供了一个列表推导示例,该示例建议使用给定周长找到直角三角形的一般方法 p,表示为元组:

λ> let rightTriangles p = [ (a,b,c) | c <- [1..(quot p 2)], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2, a + b + c == p] 

但是随着p变大,这种方法变得非常慢。

是否有通常更快但惯用的 Haskell 方法来为大型 p 完成同样的事情?

评论很好地说明了您真正需要的是更好的算法。

但是让我们尝试一些不同的东西,看看我们可以对当前代码进行哪些优化:

let rightTrianglesCubic p = [ (a,b,c) | c <- [1..quot p 2], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2, a + b + c == p]

首先,请注意我们如何遍历 [1..b] 的所有值,直到找到 a + b + c == p 的值。但唯一成立的值是 a = p - b - c,因此我们可以完全跳过循环,并将其变成二次算法:

let rightTrianglesQuadraticA p = [ (a,b,c) | c <- [1..quot p 2], b <- [1..c], let a = p - b - c, a^2 + b^2 == c^2]

这种方法有一个小问题:

λ rightTrianglesCubic 120
[(30,40,50),(24,45,51),(20,48,52)]
λ rightTrianglesQuadraticA 120
[(40,30,50),(30,40,50),(45,24,51),(24,45,51),(48,20,52),(20,48,52),(0,60,60)]

我们得到了一些额外的结果!这是因为我们忽略了a <- [1..b] 的隐含条件,即1 <= aa <= b。所以让我们把它们加回去。

let rightTrianglesQuadraticB p = [ (a,b,c) | c <- [1..quot p 2], b <- [1..c], let a = p - b - c, a^2 + b^2 == c^2, 1 <= a, a <= b]

现在我们得到了正确答案:

λ rightTrianglesQuadraticB 120
[(30,40,50),(24,45,51),(20,48,52)]

由于 b 的每个值都有唯一的 a,因此条件 1 <= aa <= b 可以表述为 b 上的条件。 1 <= a1 <= p - b - c 相同或 b <= p - c - 1a <= bp - b - c <= bp - c <= 2*b 相同或 div (p - c + 1) 2 <= b.

这让我们可以缩小循环的边界 b <- [1..c]:

let rightTrianglesQuadraticC p = [ (a,b,c) | c <- [1..quot p 2], b <- [max 1 (div (p - c + 1) 2) .. min c (p - c - 1) ], let a = p - b - c, a^2 + b^2 == c^2]

我们甚至可以缩小 c <- [1..quot p 2] 的界限,注意为了 a < b < ca+b+c == p,我们必须有 c > p/3:

let rightTrianglesQuadraticD p = [ (a,b,c) | c <- [1 + quot p 3..quot p 2], b <- [max 1 (div (p - c + 1) 2) .. min c (p - c - 1) ], let a = p - b - c, a^2 + b^2 == c^2]

这就是优化此特定代码所能达到的程度。为了进一步提高性能,我们需要完全切换算法。