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