此答案分爲兩部分。第一部分直接解決問題。第二部分爲了深入研究第一部分背後的數學問題,從切線(字面上)切入:它可能因此被證明是有限興趣的難題材料,但我認爲一些極端主義者可能會喜歡它。
我到目前爲止看到整齊做出使用列表理解或他們的單子等同的答案,但他們使用平等排除重複,因此需要額外的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