Haskell 中 2 个列表的笛卡尔积
我希望在 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)]
这不是一个实际的家庭作业问题,也与任何此类问题无关,但解决此问题的方式可能对我遇到的问题有所帮助。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(16)
如果您想要的只是笛卡尔积,则上述任何答案都可以。但通常情况下,笛卡尔积是达到目的的一种手段。通常,这意味着将元组的元素绑定到一些变量,
x
和y
,然后对它们调用一些函数fx y
。如果这就是计划,那么您最好直接使用完整的 monad:这将生成列表
[f 1 6, f 1 8, f 1 10, f 2 6, f 2 8, f 2 10]
。If all you want is the Cartesian product, any of the above answers will do. Usually, though, the Cartesian product is a means to an end. Usually, this means binding the elements of the tuple to some variables,
x
andy
, then calling some functionf x y
on them. If this is the plan anyway, you might be better off just going full monad:This will produce the list
[f 1 6, f 1 8, f 1 10, f 2 6, f 2 8, f 2 10]
.让我总结一下计算 2 个列表的笛卡尔积的所有方法。其中大部分已经提到过,但是将它们放在一个地方会很有用。我还在最后添加了更多选项,因此这个答案不仅仅是其他答案的汇编。
liftM2 (,)
sequence
-- 生成列表列表(,) <$>; xs <*>; ys
[(x, y) | x <- xs, y <- ys]
do
表示法xs >>= (\x -> ys >>= ( \y -> (x, y)))
(ys >>=) 。 (,) =<< xs
MonadPlus
-- xs `mplus` ysLet me provide a summary of all the ways we can take the cartesian product of 2 lists. Most of these have already been mentioned, but it's useful to have them in one place. I've also added some more options at the end, so that this answer isn't only a compilation of the other answers.
liftM2 (,)
sequence
-- generates a list of lists(,) <$> xs <*> ys
[(x, y) | x <- xs, y <- ys]
do
notationxs >>= (\x -> ys >>= (\y -> (x, y)))
(ys >>=) . (,) =<< xs
MonadPlus
-- xs `mplus` ys使用列表推导式这非常容易。要获得列表
xs
和ys
的笛卡尔积,我们只需为每个元素获取元组(x,y)
xs
中的 >x 和ys
中的每个元素y
。这给了我们以下列表理解:
This is very easy with list comprehensions. To get the cartesian product of the lists
xs
andys
, we just need to take the tuple(x,y)
for each elementx
inxs
and each elementy
inys
.This gives us the following list comprehension:
正如其他答案所指出的,使用列表理解是在 Haskell 中执行此操作的最自然的方法。
然而,如果您正在学习 Haskell 并希望致力于开发有关 Monad 等类型类的直觉,那么找出为什么这个稍短的定义是等效的是一个有趣的练习:
您可能永远不会想要用真实的代码编写它,但基本思想是你在 Haskell 中经常看到的:我们使用
liftM2
来提升非单子函数(,) 到一个 monad 中——在本例中具体是列表 monad。
如果这没有任何意义或没有用,那就忘记它吧——这只是看待问题的另一种方式。
As other answers have noted, using a list comprehension is the most natural way to do this in Haskell.
If you're learning Haskell and want to work on developing intuitions about type classes like
Monad
, however, it's a fun exercise to figure out why this slightly shorter definition is equivalent:You probably wouldn't ever want to write this in real code, but the basic idea is something you'll see in Haskell all the time: we're using
liftM2
to lift the non-monadic function(,)
into a monad—in this case specifically the list monad.If this doesn't make any sense or isn't useful, forget it—it's just another way to look at the problem.
如果您的输入列表属于相同类型,则可以使用
sequence
(使用List
monad)获取任意数量列表的笛卡尔积。这将为您提供列表列表而不是元组列表:If your input lists are of the same type, you can get the cartesian product of an arbitrary number of lists using
sequence
(using theList
monad). This will get you a list of lists instead of a list of tuples:有一种非常优雅的方法可以使用应用函子来做到这一点:
基本思想是将函数应用于“包装”参数,例如,
在列表的情况下,该函数将应用于所有组合,因此您所要做的就是使用
(,)
将它们“元组”。请参阅http://learnyouahaskell.com/functors-applicative-functors-and- monoids#applicative-functors 或(更理论化)http: //www.soi.city.ac.uk/~ross/papers/Applicative.pdf 了解详细信息。
There is a very elegant way to do this with Applicative Functors:
The basic idea is to apply a function on "wrapped" arguments, e.g.
In case of lists, the function will be applied to all combinations, so all you have to do is to "tuple" them with
(,)
.See http://learnyouahaskell.com/functors-applicative-functors-and-monoids#applicative-functors or (more theoretical) http://www.soi.city.ac.uk/~ross/papers/Applicative.pdf for details.
其他答案假设两个输入列表是有限的。通常,惯用的 Haskell 代码包含无限列表,因此值得简要评论一下如何在需要时生成无限笛卡尔积。
标准方法是使用对角化;将一个输入沿顶部写入,将另一个输入沿左侧写入,我们可以编写一个包含完整笛卡尔积的二维表,如下所示:
当然,跨任何单行工作都会在到达下一行之前为我们提供无限个元素排;同样,按列进行操作将是灾难性的。但是我们可以沿着向下和向左的对角线走,每次到达网格边缘时,我们都会从稍微向右一点的地方开始。
...等等。按照顺序,这将为我们提供:
要在 Haskell 中进行编码,我们可以首先编写生成二维表的版本:
对角化的低效方法是首先沿着对角线迭代,然后沿着每个对角线的深度迭代,拉出每次都选择适当的元素。为了简单起见,我假设两个输入列表都是无限的,因此我们不必搞乱边界检查。
这个实现有点不幸:重复的列表索引操作
!!
随着我们的进行变得越来越昂贵,给出了相当糟糕的渐近性能。更有效的实现将采用上述想法,但使用拉链来实现。因此,我们将无限网格分成三个形状,如下所示:左上角的三角形将是我们已经发出的位;右上角的四边形将是已部分发射但仍对结果有贡献的行;底部矩形将是我们尚未开始发出的行。首先,上面的三角形和上面的四边形将是空的,底部的矩形将是整个网格。在每一步中,我们可以发出上四边形中每行的第一个元素(本质上将斜线移动一个),然后从底部矩形到上四边形添加一个新行(本质上将水平线向下移动一个) )。
虽然这看起来有点复杂,但效率明显更高。它还处理我们在更简单的版本中进行的边界检查。
但当然,您不应该自己编写所有这些代码!相反,您应该使用 universe 包。在
Data.Universe 中。助手
,有(+*+)
,将上面的cartesian2d
和封装在一起对角线
函数仅给出笛卡尔积运算:如果该结构变得有用,您还可以看到对角线本身:
如果您有许多要一起乘积的列表,则迭代
(+*+)
可以对某些名单有不公平的偏见;您可以使用choices :: [[a]] -> [[a]]
满足您的 n 维笛卡尔积需求。Other answers assume that the two input lists are finite. Frequently, idiomatic Haskell code includes infinite lists, and so it is worthwhile commenting briefly on how to produce an infinite Cartesian product in case that is needed.
The standard approach is to use diagonalization; writing the one input along the top and the other input along the left, we could write a two-dimensional table that contained the full Cartesian product like this:
Of course, working across any single row will give us infinitely elements before it reaches the next row; similarly going column-wise would be disastrous. But we can go along diagonals that go down and to the left, starting again in a bit farther to the right each time we reach the edge of the grid.
...and so on. In order, this would give us:
To code this in Haskell, we can first write the version that produces the two-dimensional table:
An inefficient method of diagonalizing is to then iterate first along diagonals and then along depth of each diagonal, pulling out the appropriate element each time. For simplicity of explanation, I'll assume that both the input lists are infinite, so we don't have to mess around with bounds checking.
This implementation is a bit unfortunate: the repeated list indexing operation
!!
gets more and more expensive as we go, giving quite a bad asymptotic performance. A more efficient implementation will take the above idea but implement it using zippers. So, we'll divide our infinite grid into three shapes like this:The top left triangle will be the bits we've already emitted; the top right quadrilateral will be rows that have been partially emitted but will still contribute to the result; and the bottom rectangle will be rows that we have not yet started emitting. To begin with, the upper triangle and upper quadrilateral will be empty, and the bottom rectangle will be the entire grid. On each step, we can emit the first element of each row in the upper quadrilateral (essentially moving the slanted line over by one), then add one new row from the bottom rectangle to the upper quadrilateral (essentially moving the horizontal line down by one).
Although this looks a bit more complicated, it is significantly more efficient. It also handles the bounds checking that we punted on in the simpler version.
But you shouldn't write all this code yourself, of course! Instead, you should use the universe package. In
Data.Universe.Helpers
, there is(+*+)
, which packages together the abovecartesian2d
anddiagonal
functions to give just the Cartesian product operation:You can also see the diagonals themselves if that structure becomes useful:
If you have many lists to product together, iterating
(+*+)
can unfairly bias certain lists; you can usechoices :: [[a]] -> [[a]]
for your n-dimensional Cartesian product needs.实现此目的的另一种方法是使用应用程序:
Yet another way to accomplish this is using applicatives:
还有另一种方法,使用
do
表示法:Yet another way, using the
do
notation:正确的方法是使用列表推导式,正如其他人已经指出的那样,但是如果您出于任何原因想在不使用列表推导式的情况下执行此操作,那么您可以这样做:
The right way is using list comprehensions, as other people have already pointed out, but if you wanted to do it without using list comprehensions for any reason, then you could do this:
好吧,一种非常简单的方法是使用列表理解:
我想这就是我将如何做到这一点,尽管我不是 Haskell 专家(无论如何)。
Well, one very easy way to do this would be with list comprehensions:
Which I suppose is how I would do this, although I'm not a Haskell expert (by any means).
像这样的东西:
something like:
这是一项
测序
的工作。它的一元实现可能是:正如您可能注意到的,上面类似于纯函数的
map
实现,但采用一元类型。因此你可以将其简化为It's a job for
sequence
ing. A monadic implementation of it could be:As you may notice, the above resembles the implementation of
map
by pure functions but in monadic type. Accordingly you can simplify it down to只是为爱好者添加了一种方法,仅使用递归模式匹配。
Just adding one more way for the enthusiast, using only recursive pattern matching.
这是我对 n 元笛卡尔积的实现:
Here is my implementation of n-ary cartesian product:
没有列表理解的递归模式匹配
Recursive pattern matching with out List comprehension