此答案分为两部分。第一部分直接解决问题。第二部分为了深入研究第一部分背后的数学问题,从切线(字面上)切入:它可能因此被证明是有限兴趣的难题材料,但我认为一些极端主义者可能会喜欢它。
我到目前为止看到整齐做出使用列表理解或他们的单子等同的答案,但他们使用平等排除重复,因此需要额外的Eq
约束。这里有一个解决方案,它使得两个不同的职位中的所有元素对成对。首先,我写了一个方便的函数,它用一系列其他位置上的元素列表来装饰列表中的每个元素:所有“选择一个并离开其他位置”的方法。每当列表被用于收集东西以进行选择而不用替换时,这非常有用,而且我发现我使用了很多东西。
picks :: [x] -> [(x, [x])]
picks [] = []
picks (x : xs) = (x, xs) : [(y, x : ys) | (y, ys) <- picks xs]
注意map fst . picks = id
,使结果的每个位置选定元是从原来的列表中位置的元素:这就是我的意思是“装饰”。
现在很容易选择两个,使用与其他答案相同的列表理解方法。但不是从列表本身中选择第一个组件,我们可以从其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
操作使我们向根如果我们zippering约在一棵树上其节点被视为子树的容器。
我们也应该期望InContext f
是一个comonad,与
counit :: InContext f x -> x
counit = selected
伸出选定的元素和
cojoin :: InContext f x -> InContext f (InContext f x)
装饰的每一个元素与它的背景下,显示出所有可能的方式你可以再聚焦,选择一个不同的元素。
不可估量的彼得汉考克曾经向我建议,我们也应该期望能够“向下”(意思是“远离根部”),收集所有可能的方法来从上下文中选择元素上下文整个结构。
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])]
装饰的每一个元素不够的东西有点像它的背景:从刚才的其他元素的列表中,你看不到的地方“洞”是。实际上,
∂[] 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
只是(:)
,并且,直到行李相等(即排列),第二定律为 确实是成立。
看袋子的另一种方式是作为电源系列。一个包是任意大小的元组的选择,具有所有可能的排列组合(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
(或和^x),这是因其本身的衍生而闻名。
因此,唷!你走了。自然而然地由指数函数的数据类型解释产生的操作(它自己的衍生物)是用于解决基于选择而无需替换的问题的便利工具包。
谢谢!我完全忘记了列表理解。 – user1742646