按等价关系列出的组列表

时间:2011-11-24 20:26:21

标签: list haskell

我在R集上有一个equivalence relation A。如何在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

那么,如何基于[Sometuple]谓词在equal上构建所有等价类?这样的事情:

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)]]

6 个答案:

答案 0 :(得分:12)

如果您可以定义兼容的订购关系,则可以使用

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

答案 1 :(得分:1)

这里使用的正确数据结构是disjoint set(Tarjan)。 Conchon and Filliâtre描述了这种结构的纯粹功能性,持久性实现。有一个实现on Hackage

答案 2 :(得分:1)

以下是丹尼尔建议的略微变化:

由于等价类对一组值进行分区(意味着每个值只属于一个类),因此可以使用值来表示其类。然而,在许多情况下,每个班级选择一个规范代表是很自然的。在您的示例中,您可以使用代表类(0,x,0)的{​​{1}}。因此,您可以按如下方式定义代表性函数:

{ (0,0,0), (0,0,1), (1,0,0), (1,0,1), (2,0,0), ... }

现在,根据定义,representative :: Sometuple -> Sometuple representative (_,x,_) = (0,x,0) equal a b相同。因此,如果按代表对值列表进行排序 - 假设我们正在处理(representative a) == (representative b)的成员 - 那么同一等价类的成员最终会彼此相邻,并且可以按普通{{1}进行分组}。

您正在寻找的功能因此变为:

Ord

丹尼尔的建议是这种方法的概括。我基本上提出了一个特定的排序关系(即代表比较),可以在很多用例中轻松推导出来。

警告1:您需要确保根据groupByequivalenceClasses :: Ord a => (a -> a) -> [a] -> [[a]] equivalenceClasses rep = groupBy ((==) `on` rep) . sortBy (compare `on` rep) ,相同/不同等同类的代表实际上是相等/不同的。如果这两个函数测试结构相等性,那么情况总是如此。

警告2:从技术上讲,你可以放宽(==)

的类型
compare

答案 3 :(得分:1)

其他人指出,如果不对等关系建立一些额外的结构,就很难有效地解决这个问题。如果回想起数学中的定义,则等价关系等效于商映射(即从您的集合到等价类的函数)。我们可以编写一个Haskell函数,该函数给出商图(或者它的同构图)以及它的共域的一些不错的属性,并通过等价关系进行分组。我们还可以基于商图定义等价。

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) [...]

答案 4 :(得分:0)

以下解决方案在小数据上(运行时间在几秒钟内)的性能比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

答案 5 :(得分:0)

Turns out there is a similar function in GHC.Exts.

λ import GHC.Exts
λ groupWith snd [('a', 1), ('b', 2), ('c', 1)]
[[('a',1),('c',1)],[('b',2)]]

它要求你定义一个从你的类型到具有兼容类型的函数 Ord 其中 Eq 与您的等效概念一致。 (此处为snd。) 从本质上讲,您 可以将此函数视为指向等价类集合的箭头,也称为商映射。

Thanks to Olaf for pointing out.