将列表拆分为可能的元组列表

时间:2012-10-13 01:18:40

标签: haskell tuples list-comprehension

我需要将列表拆分为所有可能元组的列表,但我不确定如何这样做。

例如:

pairs ["cat","dog","mouse"]

应该导致:

[("cat","dog"), ("cat","mouse"), ("dog","cat"), ("dog","mouse"), ("mouse","cat"), ("mouse","dog")]

我能够形成前两个,但我不确定如何得到其余的。

这是我到目前为止所拥有的:

pairs :: [a] -> [(a,a)]
pairs (x:xs) = [(m,n) | m <- [x], n <- xs]

6 个答案:

答案 0 :(得分:99)

这个答案分为两部分。第一部分直接解决了这个问题。第二部分是切线(字面意思),以便在第一部分背后的数学中挖掘:它可能因此被证明是有限兴趣的困难材料,但我认为一些极端分子可能会喜欢它。

到目前为止,我已经看到的答案巧妙地使用了列表推导或它们的monadic等价物,但是他们使用 equality 来排除重复,因此需要额外的Eq约束。这是一个解决方案,它使所有元素对分成两个不同的位置

首先,我编写了一个方便的函数,用其他位置的元素列表来装饰列表的每个元素:选择一个并留下其他元素的所有方法&#34;。每当使用列表收集选择内容时都非常有用 - 无需替换,这是我发现我经常使用的东西。

picks :: [x] -> [(x, [x])]
picks [] = []
picks (x : xs) = (x, xs) : [(y, x : ys) | (y, ys) <- picks xs]

请注意map fst . picks = id,以便结果的每个位置中的所选元素是原始列表中该位置的元素:那是我的意思&#34;装饰&#34;

现在使用与其他答案相同的列表理解方法可以轻松选择两个。但是,我们不是从列表本身中选择第一个组件,而是从picks中进行选择,同时获取第二个组件的候选列表。

allPairs :: [x] -> [(x, x)]
allPairs xs = [(y, z) | (y, ys) <- picks xs, z <- ys]

同样容易抓住三元组,两次picks

allTriples :: [x] -> [(x, x, x)]
allTriples ws = [(x, y, z) | (x, xs) <- picks ws, (y, ys) <- picks xs, z <- ys]

为了保持一致性,在两者中写(z, _) <- picks ys而不是z <- ys时,使代码效率稍差,几乎很诱人。

如果输入列表没有重复项,则您不会在输出中获得任何重复的元组,因为元组从不同的位置获取它们的元素。但是,你会得到

Picks> allPairs ["cat", "cat"]
[("cat","cat"),("cat","cat")]

为了避免这种情况,请随意使用allPairs . nub,它会在选择之前删除重复项,并再次要求元素类型的Eq实例。


仅针对极端分子:容器,微积分,comonads和组合学啊!

picks是一个更一般的结构的一个实例,来自微积分。这是一个有趣的事实,对于任何给定的容器类型的仿函数f,其数学导数∂f代表f - 删除了一个元素的结构。例如,

newtype Trio x = Trio (x, x, x)   -- x^3

有衍生物

data DTrio x = Left3 ((), x, x) | Mid3 (x, (), x) | Right3 (x, x, ())  -- 3*x^2

许多操作可以与此构造相关联。想象一下,我们可以真正使用∂(我们可以使用类型系列对其进行编码)。然后我们可以说

data InContext f x = (:-) {selected :: x, context :: ∂f x}

给出一种由上下文修饰的选定元素。我们当然希望有这个操作

plug :: InContext f x -> f x   -- putting the element back in its place

如果我们在一个树中拉链,其节点被视为子树的容器,则此plug操作会将我们移向根。

我们还应该期望InContext f成为comonad,

counit :: InContext f x -> x
counit = selected

预测所选元素和

cojoin :: InContext f x -> InContext f (InContext f x)

用其上下文装饰每个元素,显示所有可能的方式重新聚焦,选择不同的元素。

不可估量的彼得汉考克曾向我建议,我们也应该期待能够'#34; (意思是&#34;远离根&#34;),收集从整个结构中选择上下文元素的所有可能方法。

picks :: f x -> f (InContext f x)

应该使用其上下文装饰输入x - 结构中的每个f - 元素。我们应该期待

fmap selected . picks = id

这是我们之前的法律,但也是

fmap plug (picks fx) = fmap (const fx) fx

告诉我们每个装饰元素都是原始数据的分解。我们上面没有这个法律。我们有

picks :: [x] -> [(x, [x])]

装饰每个元素并不完全有点像它的上下文:从其他元素的列表中,你不能看到&#34;洞&#34;是。事实上,

∂[] x = ([x], [x])

将孔之前的元素列表与孔之后的元素分开。可以说,我应该写

picks :: [x] -> [(x, ([x], [x]))]
picks [] = []
picks (x : xs) = (x, ([], xs)) : [(y, (x : ys, ys')) | (y, (ys, ys')) <- picks xs]

这也是非常有用的操作。

但真正发生的事情是非常明智的,只是轻微的滥用。在我最初编写的代码中,我在本地使用[]来表示有限行李无序列表。行包是没有特定位置概念的列表,因此如果您选择一个元素,其上下文就是其余元素的包。确实

∂Bag = Bag   -- really? why?

所以picks的正确概念确实是

picks :: Bag x -> Bag (x, Bag x)

Bag代表[]以及我们拥有的内容。此外,对于行李,plug只是(:),并且,直到行李平等(即排列),picks 的第二定律保持。

另一种看待行李的方式是作为动力系列。包是任意大小的元组的选择,所有可能的排列( n!,大小 n )被识别。所以我们可以将它组合起来作为一个以阶乘为导向的大功率,因为​​你必须将x ^ n除以n!考虑到每个n的事实!您可以选择x的订单会为您提供相同的包。

 Bag x = 1 + x + x^2/2! + x^3/3! + ...

所以

∂Bag x = 0 + 1 + x      + x^2/2! + ...

将系列转移到侧面。实际上,您可能已经认识到Bag的幂级数是exp(或 e ^ x),它以其自身的衍生而闻名。

所以,p!你去吧由指数函数的数据类型解释自然产生的操作,作为其自身的衍生物,是用于解决基于选择 - 无替换的问题的方便工具包。

答案 1 :(得分:24)

您可以使用列表理解:

allpairs :: Eq a => [a] -> [(a,a)]
allpairs xs = [ (x1,x2) | x1 <- xs, x2 <- xs, x1 /= x2 ]

答案 2 :(得分:5)

我的方法,有点类似于其他人的方法。它不需要Eq

allpairs :: [t] -> [(t,t)]
allpairs [] = []
allpairs [_] = []
allpairs (x:xs) = concatMap (\y -> [(x,y),(y,x)]) xs ++ allpairs xs

答案 3 :(得分:2)

另一种可能性是使用monadic表示法:

pairs :: (Eq a) => [a] -> [(a,a)]
pairs l = do
    x <- l
    y <- l
    guard (x /= y)
    return (x, y)

(此pairs定义的最常见类型为(MonadPlus m, Eq a) => m a -> m (a,a),但我相信除了MonadPlus之外,没有[]的实例才有意义。)

答案 4 :(得分:2)

import Control.Applicative

pairs xs = filter (uncurry (/=)) $ (,) <$> xs <*> xs

答案 5 :(得分:2)

pairs = (filter.uncurry) (/=) . (join.liftA2) (,)