【问题标题】:How are dependent ranges computed in a list comprehension?如何在列表推导中计算依赖范围?
【发布时间】:2012-09-02 23:13:41
【问题描述】:

我目前正在浏览Learn You a Haskell for Great Good!,我对Chapter 2 中的倒数第二个示例感到困惑。

作为一种生成三元组的方法,表示所有边都是小于或等于 10 的整数的所有直角三角形,他给出了以下定义:

rightTriangles = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2] 

我特别困惑的是b 绑定到一个范围从1 到c 的列表,a 也是如此。如果我的理解是正确的,c 将被评估为它绑定到的列表中的所有值,但我仍然看不到该范围内的c 使用哪个值(例如c 的所有值,只有第一个c等)

如果不是太多,一步一步解释如何评估会很棒。 :)

提前致谢!

【问题讨论】:

    标签: haskell list-comprehension evaluation


    【解决方案1】:

    让我们考虑两个更简单的列表推导:

    ex1 = [(a,b) | a <- [1..3], b <- [1..3]]
    
    ex2 = [(a,b) | a <- [1..3], b <- [1..a]]
    

    它们几乎相同,但在第二种情况下,b 的范围从 1a,而不是 13。让我们考虑一下它们等于什么;我已经对它们的值进行了格式化,以便说明问题。

    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 为其列表中的每个可能值。
        • (a,b) 是输出列表的一个元素

    或者,改写为:“对于a 的每个可能值,针对b 的每个可能值计算(a,b)。”如果您查看结果的顺序,会发生以下情况:

    1. 对于前三个元素,a 等于 1,我们看到它与 b 的每个值配对。
    2. 对于接下来的三个元素,a 等于 2,我们可以看到 b 的每个值。
    3. 最后,对于最后三个元素,a 等于 3,我们可以看到 b 的每个值。

    在第二种情况下,会发生很多相同的事情。但是因为a 被选中首先b 可以依赖它。因此:

    1. 首先,a 等于1,我们看到它与b 的每个可能值配对。因为b &lt;- [1..a],就是b &lt;- [1..1],所以只有一个选项。
    2. 在一个元素之后,a 等于2,我们看到它与b 的所有可能值配对。现在这意味着b &lt;- [1..2],所以我们得到两个结果。
    3. 最后,a 等于3,所以我们选择b &lt;- [1..3];这为我们提供了完整的三个结果集。

    换句话说,因为列表推导依赖于排序,您可以利用它。看到这一点的一种方法是想象将这些列表推导转换为 嵌套 列表推导:

    ex1 = concat [ [(a,b) | b <- [1..3]] | a <- [1..3] ]
    
    ex2 = concat [ [(a,b) | b <- [1..a]] | a <- [1..3] ]
    

    要获得正确的行为,a &lt;- [1..3] 必须在外面;这确保了bs 的变化比as 快。希望它可以清楚地说明b 可以如何依赖a。另一种翻译(基本上是使用in the Haskell 2010 Report)是:

    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,它必须在翻译后的表达式的outside,即使它在inside的列表理解。 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 &lt;- list 行读为“对于list 中的每个x”,因此将其视为嵌套循环:

    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 在此解释中仅跳到 innermost 循环的下一次迭代。你也可以写成

    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 的第 2 章时应该完全理解do-notation Haskell :-)

    【讨论】:

    • 就像你说的那样,这个答案有些超出我的想象,但由于第一个非常清晰的示例,我明确理解了核心概念。随着我在 Haskell 中的进步,我会回到这一点,并希望能从中得到更多。无论如何,谢谢你!总体来说答案很好!
    【解决方案2】:

    看到你有命令式编程的经验,简短的回答是:类似于这个for嵌套(伪代码):

    for(c = 1; c <= 10; c++) {
        for(b = 1; b <= c; b++) {
            for(a = 1; a <= b; a++) {
                if(a ^ 2 + b ^ 2 == c ^ 2) {
                    list.append((a, b, c));
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-08-27
      • 1970-01-01
      • 2019-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多