【问题标题】:Calculate n-ary Cartesian Product计算 n 元笛卡尔积
【发布时间】:2011-03-24 04:12:19
【问题描述】:

给定两个列表,我可以生成 所有排列的列表这两个列表的笛卡尔积:

permute :: [a] -> [a] -> [[a]]
permute xs ys = [ [x, y] | x <- xs, y <- ys ]

Example> permute [1,2] [3,4] == [ [1,3], [1,4], [2,3], [2,4] ]

我如何扩展 permute 以便不是采用两个列表,而是采用一个列表(长度 n)并返回一个列表列表(长度 n)

permute :: [[a]] -> [[a]]

Example> permute [ [1,2], [3,4], [5,6] ]
            == [ [1,3,5], [1,3,6], [1,4,5], [1,4,6] ] --etc

我在 Hoogle 上找不到任何相关内容。与签名匹配的唯一函数是 transpose,它不会产生所需的输出。

编辑:我认为它的 2-list 版本本质上是 Cartesian Product,但我无法完全实现 n-ary Cartesian Product。有什么指点吗?

【问题讨论】:

    标签: haskell combinatorics cartesian-product


    【解决方案1】:
    Prelude> sequence [[1,2],[3,4],[5,6]]
    [[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]]
    

    【讨论】:

    • 虽然序列确实解决了问题,但我对它的工作原理非常感兴趣。 implementation 使用单子;有没有办法在不使用单子的情况下计算产品? (例如,在不包含 monad 的语言中)
    • @BleuM937:对于列表单子,sequence 表示“对于第一个列表中的每个元素,将其添加到通过对剩余列表进行排序获得的每个列表之前”。这基本上是使用右折叠编写笛卡尔积的最明显方式。
    • 会赞成这是你告诉我们如何这到底是怎么回事
    【解决方案2】:

    我发现 Eric Lippert 在computing Cartesian product with LINQ 上的文章非常有助于提高我对正在发生的事情的理解。这是一个或多或少的直接翻译:

    cartesianProduct :: [[a]] -> [[a]]
    cartesianProduct sequences = foldr aggregator [[]] sequences
                       where aggregator sequence accumulator = 
                             [ item:accseq |item <- sequence, accseq <- accumulator ]
    

    或者使用更多“Haskell-y”简洁、无意义的参数名称;)

    cartesianProduct = foldr f [[]]
                        where f l a = [ x:xs | x <- l, xs <- a ]
    

    这最终与发布的 sclv 非常相似。

    【讨论】:

    • 其实和sclv的一样,只是语法有一些区别。此外,您知道这一点(已经编写了翻译),但对于其他任何人:请注意,Eric Lippert 的示例使用 left 折叠而不是右折叠,但这没有区别,因为该函数在无论如何,列表的脊椎(通常与 sequence 一样)。
    【解决方案3】:

    这是我的简单实现方式,仅使用列表推导。

    crossProduct :: [[a]] -> [[a]]
    crossProduct (axis:[]) = [ [v] | v <- axis ]
    crossProduct (axis:rest) = [ v:r | v <- axis, r <- crossProduct rest ]
    

    【讨论】:

      【解决方案4】:

      作为 jleedev 答案的补充(无法在 cmets 中格式化):

      用列表函数快速替换单子函数:

      sequence ms = foldr k (return []) ms
         where
          k m m' = do { x <- m; xs <- m'; return (x:xs) }
      

      ....

          k m m' = m >>= \x -> m' >>= \xs -> [x:xs]
          k m m' = flip concatMap m $ \x -> flip concatMap m' $ \xs -> [x:xs]
          k m m' = concatMap (\x -> concatMap (\xs -> [x:xs]) m') m
      

      ....

      sequence ms = foldr k ([[]]) ms
         where
           k m m' = concatMap (\x -> concatMap (\xs -> [x:xs]) m') m
      

      【讨论】:

      • 这可以通过消除k m m' = concatMap (\x -&gt; map (x:) m') m 中多余的串联来进一步简化。也可以将其写为 [ x:xs | x &lt;- m, xs &lt;- m' ] 之类的列表理解。
      【解决方案5】:

      如果你想对输出有更多的控制,你可以使用列表作为应用函子,例如:

      (\x y z -> [x,y,­z]) <$>  [1,2]­ <*> [4,5]­ <*> [6,7]
      

      假设您想要一个元组列表:

      (\x y z -> (x,y,­z)) <$>  [1,2]­ <*> [4,5]­ <*> [6,7]
      

      而且它看起来也很酷......

      【讨论】:

        【解决方案6】:

        您可以通过两种方式做到这一点:

        1. 使用列表理解
          cp :: [[a]] -> [[a]]
          cp []       = [[]]
          cp (xs:xss) = [ x:ys | x <- xs, ys <- cp xss ]
        
        1. 使用折叠
          cp1 :: [[a]] -> [[a]]
          cp1 xs = foldr f [[]] xs
                where f xs xss = [x:ys | x <- xs, ys <- xss]
        

        【讨论】:

          猜你喜欢
          • 2017-03-07
          • 2012-11-18
          • 2013-04-20
          • 2021-11-19
          • 1970-01-01
          • 2021-02-17
          • 2022-06-25
          • 1970-01-01
          • 2011-01-31
          相关资源
          最近更新 更多