为什么Data.Set需要元素作为Ord的实例?

时间:2014-01-02 10:09:11

标签: haskell set

这不起作用

data Cutlery = Knife | Fork deriving (Show,Eq)
let x = [Knife,Fork]
let set1 = Set.fromList x

定义时

data Cutlery = Knife | Fork deriving (Show,Ord,Eq)

解决了这个问题,但没有意义。 Data.Set是否与集合的数学定义不同?

4 个答案:

答案 0 :(得分:19)

A Data.Set捕获集合的数学抽象,但它不相同。主要区别在于Data.Set要求对其元素进行排序,而数学集只要求其元素具有可比性。

要求Ord的原因是效率。通过定义

来构建集抽象是完全可能的
data Set a = Set [a]

即。在引擎盖下它只是一个列表,我们确保我们永远不会插入重复的元素。 eleminsert操作将是

elem a (Set as) = any (a ==) as

insert a (Set as) | a `elem` as = Set as
                  | otherwise   = Set (a:as)

但是,这意味着eleminsert都是O( n )操作。如果我们想要做的比这更好,标准方法是

  1. 将元素存储在平衡二叉树(需要Ord实例)
  2. 散列元素并将它们存储在一个数组中(需要一个Hashable实例)。
  3. TreeSet中

    Data.Set的作者选择的实现是使用二叉树,您可以通过转到source看到。实施或多或少

    data Set a = Bin a (Set a) (Set a)
               | Tip
    

    现在您可以将elem函数编写为

    elem :: Ord a => a -> Set a -> Bool
    elem = go
      where
        go _  Tip = False
        go x (Bin y l r) = case compare x y of
          LT -> go x l
          GT -> go x r
          EQ -> True
    

    这是一个O(log n )操作,而不是O( n )。插入比较棘手(因为你需要保持树平衡)但类似。

    HashSet的

    在哈希集中,在插入和删除元素时不直接比较元素。相反,每个元素散列为整数,并存储在基于该整数的位置。

    理论上,这不需要Ord实例。在实践中,您需要一些方法来跟踪散列到相同值的多个元素,Data.HashSet的开发人员选择的方法是将多个元素存储在常规Data.Set中,因此它会变为毕竟你需要Ord实例!

    data HashSet a = HashSet (Data.IntMap.IntMap (Data.Set.Set a))
    

    它本可以写成

    data HashSet a = HashSet (Data.IntMap.IntMap [a])
    
    相反,如果有许多元素必须具有相同的值,那么会以某些低效率为代价来删除Ord要求。

答案 1 :(得分:7)

  

Data.Set是否与集合的数学定义不同?

显然,数学集可以是无数无限的 - 你无法用计算机,甚至图灵机来表示这一点。

但你要找的答案是这样的:Data.Set是一种基于二叉树的数据类型,需要对元素进行总线性排序才能知道是否放置并稍后在左侧或右侧找到某些内容节点的子树。因此,虽然可以实现没有Ord约束的set数据类型,但这种特殊的,更有效的实现不会。

答案 2 :(得分:1)

答案 3 :(得分:0)

这是为了提高效率。 Data.Set实施为二进制搜索树(又名有序已排序的二进制树)。使用这种数据结构意味着我们可以编写一个查找函数member,该函数采用对数时间,O(logn)时间,而不是线性时间,O(n)。通过对元素进行排序,我们可以在执行查找时执行指数级较少的比较。

来自Wikipedia

  

二进制搜索树将其密钥保存在排序顺序中,以便查找和其他操作可以使用二进制搜索的原则。 ...每次查找,插入或删除都需要与树中存储的项目数对数成比例。

如果元素不是Ord的实例,那么就没有办法对二叉搜索树元素进行排序 - 我们只能形成二叉树,而不是二叉搜索树。因此,我们无法快速查找。