【问题标题】:Cartesian product over a list of lists in HaskellHaskell中列表列表的笛卡尔积
【发布时间】:2015-12-05 05:13:21
【问题描述】:

给定一个长度为x 的列表列表,其中所有子列表的长度相同y,输出长度为x 的y^x 列表,其中包含每个子列表中的一项。

示例(x = 3、y = 2):

[ [1, 2], [3, 4], [5, 6] ]

输出(2^3 == 8不同的输出):

[ [1, 3, 5], [1, 4, 5], [1, 3, 6], [1, 4, 6],
  [2, 3, 5], [2, 4, 5], [2, 3, 6], [2, 4, 6] ]

我的研究/工作

红宝石

我编写了实际代码来执行此任务,但使用 Ruby,因为它是我最熟悉的语言。

def all_combinations(lst)
   lst.inject {|acc, new| acc.product(new).map(&:flatten) }
end

类型

输入是包含 a 类型项目的列表列表,输出也是。

allProduct :: [[a]] -> [[a]]

笛卡尔积,展平和折叠

查看我的 Ruby 解决方案让我觉得好好利用这些功能可能足以解决问题。问题是虽然笛卡尔积输出了一个元组列表,但我需要一个列表列表。

【问题讨论】:

  • allProduct = sequence.
  • @Zeta 但 Hoogle 说类型是 sequence :: Monad m => [m a] -> m [a] 而我需要 [[a]] -> [[a]]
  • m 是列表类型构造函数 ([]),在这种情况下。如果需要,您可以随时使 allProduct 的类型更具体:allProduct = sequence :: [[a]] -> [[a]]。
  • 你尝试了什么?搜索 [[a]] -> [[a]] 会返回匹配 sequence。
  • [[a]] 比m [a] 和[m a] 更具体,因为m(在sequence 的类型签名中)代表any monad,但是[] 只是Monad 的一个实例。

标签: haskell functional-programming combinatorics cartesian-product


【解决方案1】:

注意:这篇文章是用 literate Haskell 写的。另存为 *.lhs 并将其加载到 GHCi 中。

> -- remove this line if you don't have QuickCheck installed
> import Test.QuickCheck 

一个简单的递归变体

让我们从allProduct 的简单变体开始:

> allProductFirst :: [[a]] -> [[a]]
> allProductFirst []     = [[]]
> allProductFirst (x:xs) =

现在x 本身又是一个列表。假设allProduct xs 会给我们其他列表的乘积。

>    let rest = allProductFirst xs

我们需要做什么?我们需要为x 中的每个元素创建一个新列表并将它们连接在一起:

>    in concatMap (\k -> map (k:) rest) x

请注意,此变体并非 100% 正确,因为 allProduct [] 是 [[]]。

一元变体

如果我们使用Monad 的[] 实例会是什么样子?

使用do 表示法

> allProduct' :: [[a]] -> [[a]]
> allProduct' []     = [[]]
> allProduct' (x:xs) = do

我们要获取x的每一个元素

>      elementOfX <- x

并将其添加到我们的列表可以具有的所有可能的后缀上:

>      rest       <- allProduct' xs
>      return $ elementOfX : rest

这意味着我们基本上是在评估列表 monad 中的每个动作。但是有一个功能:sequence :: Monad m =&gt; [m a] -&gt; m [a]。如果我们使用m ~ [],它的类型可以特化为sequence :: [[a]] -&gt; [[a]]。

使用sequence

我们最终得到了最后一个变体:

> allProduct :: [[a]] -> [[a]]
> allProduct = sequence

测试结果

我们使用 QuickCheck 来测试它是否可能与 allProductFirst 相同 和allProduct':

> main :: IO ()
> main = do
>   quickCheck $
>     forAll (choose (1,8)) $ \n -> -- number of lists
>     forAll (choose (1,4)) $ \s -> -- number of elements per list
>     forAll (vectorOf n $ vector s) $ \xs ->
>       allProduct'     xs === allProduct (xs :: [[Integer]]) .&.
>       allProductFirst xs === allProduct xs

在 GHCi 中使用 :main,runhaskell 或编译并运行您的程序,然后您 最终应该有 100 个通过测试。

【讨论】:

  • 我们使用 QuickCheck 来证明 [...] “证明”这个词太强了。 “增强我们的信心”怎么样?
  • @Jubobs 将其更改为“测试它是否可能相同”更诚实,我认为。但我同意,这不是“证据”。
  • 这是一个很好的答案。
  • 与 Agda 或 Idris 的正确证明相比,QuickCheck 就像是伪证明。
  • @PyRulez allProduct [] 不是由 OP 定义的,所以它是模棱两可的。话虽如此,Ruby 代码在空列表中以nil 结尾,因此没有有效的 Haskell 翻译基本情况。答案中也提到了模棱两可的行为,尽管没有详细说明:“请注意,此变体并非 100% 正确,因为 allProduct [] 是 [[]]”。询问 OP allProduct [] 应该是什么。 ¯\_(ツ)_/¯
猜你喜欢
  • 2023-04-10
  • 2011-02-06
  • 2011-05-06
  • 2013-12-29
  • 2012-03-24
  • 1970-01-01
  • 2019-08-26
相关资源
最近更新 更多