簡體   English   中英

按等價關系分組列表

[英]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

原來在GHC.Exts有一個類似的功能。

λ 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM