让我们考虑两个更简单的列表推导:
ex1 = [(a,b) | a <- [1..3], b <- [1..3]]
ex2 = [(a,b) | a <- [1..3], b <- [1..a]]
它们几乎相同,但在第二种情况下,b
范围从1
to a
,不是1
to 3
。让我们考虑一下它们等于什么;我已经对它们的值进行了格式化,以便说明问题。
ex1 = [ (1,1), (1,2), (1,3)
, (2,1), (2,2), (2,3)
, (3,1), (3,2), (3,3) ]
ex2 = [ (1,1),
, (2,1), (2,2),
, (3,1), (3,2), (3,3) ]
在第一个示例中,列表推导式从[1..3]
和中提取所有可能的元素组合[1..3]
。但是由于我们谈论的是列表,而不是集合,所以它执行的顺序很重要。因此,更详细地说,ex1
真正的意思是:
或者,改写为:“对于 的每个可能值a
,计算(a,b)
的每个可能值b
。” 如果您查看结果的顺序,会发生以下情况:
- 对于前三个元素,
a
等于1
,我们看到它与 的每个值配对b
。
- 对于接下来的三个元素,
a
等于2
,我们看到 的每个值b
。
- 最后,对于最后三个元素,
a
等于3
,我们看到 的每个值b
。
在第二种情况下,同样的事情也会发生。但因为a
是先选的,b
所以可以依赖它。因此:
- 首先,
a
等于1
,我们看到它与 的每个可能值配对b
。因为b <- [1..a]
, 这意味着b <- [1..1]
, 所以只有一个选项。
- 然后,在一个元素之后
a
等于2
,我们看到它与 的每个可能值配对b
。现在这意味着b <- [1..2]
,所以我们得到两个结果。
- 最后,
a
等于3
,所以我们选择b <- [1..3]
; 这给了我们完整的三个结果集。
换句话说,因为列表推导依赖于排序,所以您可以利用它。看到这一点的一种方法是想象将这些列表推导转换为嵌套列表推导:
ex1 = concat [ [(a,b) | b <- [1..3]] | a <- [1..3] ]
ex2 = concat [ [(a,b) | b <- [1..a]] | a <- [1..3] ]
要想得到正确的行为,a <- [1..3]
必须走在外面;这确保了b
s 的变化比a
s 快。它希望能清楚地说明如何b
依赖a
. 另一种翻译(基本上是Haskell 2010 报告中使用的翻译)是:
ex1 = concatMap (\a -> [(a,b) | b <- [1..3]]) [1..3]
= concatMap (\a -> concatMap (\b -> [(a,b)]) [1..3]) [1..3]
ex2 = concatMap (\a -> [(a,b) | b <- [1..a]]) [1..3]
= concatMap (\a -> concatMap (\b -> [(a,b)]) [1..a]) [1..3]
同样,这使得嵌套非常明确,即使很难遵循。需要记住的是,如果a
要首先选择 of,它必须位于已翻译表达式的外部,即使它位于列表推导式的内部。完整的,正式的翻译rightTriangles
将是
rightTriangles =
concatMap (\c ->
concatMap (\b ->
concatMap (\a ->
if a^2 + b^2 == c^2
then [(a,b,c)]
else []
) [1..b]
) [1..c]
) [1..10]
作为旁注,另一种写法rightTriangles
如下:
import Control.Monad (guard)
rightTriangles = do c <- [1..10]
b <- [1..c]
a <- [1..b]
guard $ a^2 + b^2 == c^2
return (a,b,c)
你可能还没有使用do
符号,当然除了 之外什么都没有IO
,所以我并不是说你一定要理解这一点。但是您可以将这些x <- list
行读为“for each x
in list
”,因此将其读为嵌套循环:
rightTriangles = do
c <- [1..10] -- For each `c` from `1` to `10`, ...
b <- [1..c] -- For each `b` from `1` to `c`, ...
a <- [1..b] -- For each `a` from `1` to `b`, ...
guard $ a^2 + b^2 == c^2 -- If `a^2 + b^2 /= c^2`, then `continue` (as in C);
return (a,b,c) -- `(a,b,c)` is the next element of the output list.
请注意,在此解释中,continue
唯一会跳到最内层循环的下一次迭代。你也可以写成
rightTriangles = do c <- [1..10]
b <- [1..c]
a <- [1..b]
if a^2 + b^2 == c^2
then return (a,b,c)
else [] -- or `mzero`
最后几行说“如果a^2 + b^2 == c^2
,添加(a,b,c)
到输出列表;否则,不添加任何内容。” 我之所以提到这一点,是因为我认为以这种方式编写它可能有助于使“嵌套循环”类型的结构变得清晰,而不是因为您在阅读Learn You A Haskelldo
的第 2 章时应该完全理解 -notation :-)