我希望在Haskell中产生2个列表的笛卡尔积,但是我不知道该怎么做。笛卡尔积提供列表元素的所有组合:
xs = [1,2,3]
ys = [4,5,6]
cartProd :: [a] -> [b] -> [(a,b)]
cartProd xs ys ==> [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]
这不是一个实际的家庭作业问题,并且与任何此类问题都没有关系,但是解决这个问题的方法可能会帮助我坚持下去。
我希望在Haskell中产生2个列表的笛卡尔积,但是我不知道该怎么做。笛卡尔积提供列表元素的所有组合:
xs = [1,2,3]
ys = [4,5,6]
cartProd :: [a] -> [b] -> [(a,b)]
cartProd xs ys ==> [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]
这不是一个实际的家庭作业问题,并且与任何此类问题都没有关系,但是解决这个问题的方法可能会帮助我坚持下去。
Answers:
正如其他答案所指出的那样,在Haskell中使用列表理解是最自然的方法。
如果您正在学习Haskell并想开发有关像这样的类型类的直觉Monad
,那么弄清楚为什么这个略短的定义是等效的是一个有趣的练习:
import Control.Monad (liftM2)
cartProd :: [a] -> [b] -> [(a, b)]
cartProd = liftM2 (,)
您可能永远都不想用真实的代码编写此代码,但是基本的想法是您始终会在Haskell中看到的东西:我们正在liftM2
将非Monadic函数提升(,)
为monad,在这种情况下,特别是列出单子。
如果这没有任何意义或没有用,请忘记它-这只是解决问题的另一种方法。
liftM2
为了清楚起见在这里最初使用单子函数(更多的人听说过单子而不是应用函子?),但是您所需要的只是列表的应用函子实例,因此liftA2
将等效地工作。
应用函子有一个非常优雅的方法:
import Control.Applicative
(,) <$> [1,2,3] <*> [4,5,6]
-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]
基本思想是在“包装”参数上应用函数,例如
(+) <$> (Just 4) <*> (Just 10)
-- Just 14
如果是列表,该功能将应用于所有组合,因此您要做的就是使用来“将其组合” (,)
。
见http://learnyouahaskell.com/functors-applicative-functors-and-monoids#applicative-functors或(更多的理论)http://www.soi.city.ac.uk/~ross/papers/Applicative.pdf为细节。
其他答案假定两个输入列表是有限的。惯用的Haskell代码通常包含无限列表,因此,有必要在需要时简要说明如何产生无限笛卡尔积。
标准方法是使用对角线化。在顶部写一个输入,在左边写另一个输入,我们可以编写一个包含完整笛卡尔积的二维表,如下所示:
1 2 3 4 ...
a a1 a2 a3 a4 ...
b b1 b2 b3 b4 ...
c c1 c2 c3 c4 ...
d d1 d2 d3 d4 ...
. . . . . .
. . . . . .
. . . . . .
当然,跨任何一行工作都会在到达下一行之前为我们提供无限的元素;同样,按列进行将是灾难性的。但是,我们可以沿着向下和向左向下的对角线移动,每次到达网格的边缘时,都从向右一点的距离开始。
a1
a2
b1
a3
b2
c1
a4
b3
c2
d1
...等等。为了使我们能够:
a1 a2 b1 a3 b2 c1 a4 b3 c2 d1 ...
要在Haskell中对此进行编码,我们首先可以编写生成二维表的版本:
cartesian2d :: [a] -> [b] -> [[(a, b)]]
cartesian2d as bs = [[(a, b) | a <- as] | b <- bs]
对角化的一种无效方法是,先沿对角线,然后沿每个对角线的深度进行迭代,每次都拉出适当的元素。为了简化说明,我将假定两个输入列表都是无限的,因此我们不必弄乱边界检查。
diagonalBad :: [[a]] -> [a]
diagonalBad xs =
[ xs !! row !! col
| diagonal <- [0..]
, depth <- [0..diagonal]
, let row = depth
col = diagonal - depth
]
这种实现有点不幸:重复的列表索引操作!!
随着我们的进行而变得越来越昂贵,从而带来相当差的渐近性能。一种更有效的实现方式将采用上述想法,但要使用拉链来实现。因此,我们将无限网格划分为三个形状,如下所示:
a1 a2 / a3 a4 ...
/
/
b1 / b2 b3 b4 ...
/
/
/
c1 c2 c3 c4 ...
---------------------------------
d1 d2 d3 d4 ...
. . . . .
. . . . .
. . . . .
左上角的三角形将是我们已经发出的位;右上四边形将是已部分发射但仍会影响结果的行;而底部矩形将是我们尚未开始发射的行。首先,上面的三角形和上面的四边形将为空,而下面的矩形将是整个网格。在每个步骤中,我们都可以发射上四边形中的每一行的第一个元素(基本上将斜线移了一个),然后从底部矩形向上四边形中添加了新的一行(基本上将水平线下移了一个) )。
diagonal :: [[a]] -> [a]
diagonal = go [] where
go upper lower = [h | h:_ <- upper] ++ case lower of
[] -> concat (transpose upper')
row:lower' -> go (row:upper') lower'
where upper' = [t | _:t <- upper]
尽管这看起来有些复杂,但效率明显更高。它还处理我们在较简单版本中进行的边界检查。
但是,当然,您不应该自己编写所有这些代码!相反,您应该使用Universe包。在中Data.Universe.Helpers
,有(+*+)
,将上述内容cartesian2d
和diagonal
功能打包在一起,仅给出笛卡尔乘积运算:
Data.Universe.Helpers> "abcd" +*+ [1..4]
[('a',1),('a',2),('b',1),('a',3),('b',2),('c',1),('a',4),('b',3),('c',2),('d',1),('b',4),('c',3),('d',2),('c',4),('d',3),('d',4)]
如果该结构变得有用,您还可以看到对角线本身:
Data.Universe.Helpers> mapM_ print . diagonals $ cartesian2d "abcd" [1..4]
[('a',1)]
[('a',2),('b',1)]
[('a',3),('b',2),('c',1)]
[('a',4),('b',3),('c',2),('d',1)]
[('b',4),('c',3),('d',2)]
[('c',4),('d',3)]
[('d',4)]
如果您有很多要一起生产的列表,则迭代(+*+)
可能会不公平地偏向某些列表。您可以choices :: [[a]] -> [[a]]
根据自己的n维笛卡尔积需求使用。
正确的方法是使用列表推导,正如其他人已经指出的那样,但是如果您出于某种原因不想使用列表推导,则可以这样做:
cartProd :: [a] -> [b] -> [(a,b)]
cartProd xs [] = []
cartProd [] ys = []
cartProd (x:xs) ys = map (\y -> (x,y)) ys ++ cartProd xs ys
cartProd xs ys = xs >>= \x -> ys >>= \y -> [(x,y)]
map (\y -> (x,y))
你可以写map ((,) x)
。
这是一份工作sequence
。它的单例实现可以是:
cartesian :: [[a]] -> [[a]]
cartesian [] = return []
cartesian (x:xs) = x >>= \x' -> cartesian xs >>= \xs' -> return (x':xs')
*Main> cartesian [[1,2,3],[4,5,6]]
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]
您可能会注意到,以上内容类似于map
纯函数的实现,但采用单子类型。因此,您可以将其简化为
cartesian :: [[a]] -> [[a]]
cartesian = mapM id
*Main> cartesian [[1,2,3],[4,5,6]]
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]
这是我的n元笛卡尔积的实现:
crossProduct :: [[a]] -> [[a]]
crossProduct (axis:[]) = [ [v] | v <- axis ]
crossProduct (axis:rest) = [ v:r | v <- axis, r <- crossProduct rest ]
crossProduct = sequence
样