[英]Group list by equivalence relation
我在集合A
上有一個等價關系R
。 如何在A
上構建等價類? 這有點像groupBy
所做的,但在所有元素之間,而不僅僅是鄰居。
例如, equal
是等價關系(它是自反、對稱和傳遞的二元關系):
type Sometuple = (Int, Int, Int)
equal :: Sometuple -> Sometuple -> Bool
equal (_, x, _) (_, y, _) = x == y
它實際上是連接 2 個Sometuple
元素的謂詞。
λ> equal (1,2,3) (1,2,2)
True
那么,如何基於equal
謂詞在[Sometuple]
上構建所有等價類? 類似的東西:
equivalenceClasses :: (Sometuple -> Sometuple -> Bool) -> [Sometuple] -> [[Sometuple]]
λ> equivalenceClasses equal [(1,2,3), (2,1,4), (0,3,2), (9,2,1), (5,3,1), (1,3,1)]
[[(1,2,3),(9,2,1)],[(2,1,4)],[(0,3,2),(5,3,1),(1,3,2)]]
如果您可以定義兼容的排序關系,則可以使用
equivalenceClasses equal comp = groupBy equal . sortBy comp
這會給你O(n*log n)
復雜度。 沒有那個,我基本上看不到比O(n^2)
更好的復雜性的方法
splitOffFirstGroup :: (a -> a -> Bool) -> [a] -> ([a],[a])
splitOffFirstGroup equal xs@(x:_) = partition (equal x) xs
splitOffFirstGroup _ [] = ([],[])
equivalenceClasses _ [] = []
equivalenceClasses equal xs = let (fg,rst) = splitOffFirstGroup equal xs
in fg : equivalenceClasses equal rst
此處使用的正確數據結構是不相交集(Tarjan)。 Conchon 和 Filliâtre描述了這種結構的純函數式持久實現。 在 Hackage 上有一個實現。
以下是 Daniel 建議的細微變化:
由於等價類划分一組值(意味着每個值只屬於一個類),您可以使用一個值來表示它的類。 然而,在許多情況下,為每個類選擇一個規范代表是很自然的。 在您的示例中,您可能會使用(0,x,0)
表示類{ (0,0,0), (0,0,1), (1,0,0), (1,0,1), (2,0,0), ... }
。 因此,您可以按如下方式定義代表性函數:
representative :: Sometuple -> Sometuple
representative (_,x,_) = (0,x,0)
現在,根據定義, equal ab
與(representative a) == (representative b)
。 因此,如果您按代表對值列表進行排序——假設我們正在處理Ord
成員——,同一等價類的成員最終會彼此相鄰,並且可以按普通groupBy
分組。
您正在尋找的功能因此變為:
equivalenceClasses :: Ord a => (a -> a) -> [a] -> [[a]]
equivalenceClasses rep = groupBy ((==) `on` rep) . sortBy (compare `on` rep)
Daniel 的建議是對這種方法的概括。 我本質上提出了一個特定的排序關系(即代表比較),可以在許多用例中輕松推導出。
警告 1:根據(==)
和compare
,您需要確保相同/不同等價類的代表實際上是相等/不同的。 如果這兩個函數測試結構相等,情況總是如此。
警告 2:從技術上講,您可以將equivalenceClasses
的類型放寬到
equivalenceClasses :: Ord b => (a -> b) -> [a] -> [[a]]
其他人已經指出,如果沒有對等價關系的一些額外結構,這個問題很難有效地解決。 如果回憶數學中的定義,等價關系就相當於商映射(即從您的集合到等價類的函數)。 我們可以編寫一個 Haskell 函數,給定商映射(或者與其同構的東西)和它的 codomain 的一些很好的屬性,根據等價關系分組。 我們也可以基於商映射來定義等價。
import Data.Map
group :: Ord b => (a -> b) -> [a] -> [[a]]
group q xs = elems $ fromListWith (++) [(q x, [x]) | x <- xs]
sameClass :: Eq b => (a -> b) -> (a -> a -> Bool)
sameClass q a b = q a == q b
-- for your question
equal = sameClass (\(_,x,_) -> x)
group (\(_,x,_) -> x) [...]
以下解決方案在小數據(列表少於大約 2¹⁴ = 16384 個元素)上的執行速度比 Daniel Fischer 的略快。 它的工作原理是將元素一個一個地添加到等價類中,如果一個元素不屬於任何現有元素,則創建一個新類。
module Classify where
import qualified Data.List as List
classify :: Eq a => [a] -> [[a]]
classify = classifyBy (==)
classifyBy :: (a -> a -> Bool) -> [a] -> [[a]]
classifyBy eq = List.foldl' f [ ]
where
f [ ] y = [[y]]
f (xs@ (x: _): xss) y | x `eq` y = (y: xs): xss
| otherwise = xs: f xss y
λ import GHC.Exts
λ groupWith snd [('a', 1), ('b', 2), ('c', 1)]
[[('a',1),('c',1)],[('b',2)]]
它要求您將函數從您的類型定義為具有兼容Ord
的類型,而Eq
與您的等價概念一致。 (這里是snd
。)在分類上,您可以將此函數視為指向等價類集的箭頭,也稱為商映射。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.