Haskell 我是否需要采取明确的措施来促进与持久数据结构的共享?

Haskell 我是否需要采取明确的措施来促进与持久数据结构的共享?,haskell,data-structures,disjoint-sets,union-find,Haskell,Data Structures,Disjoint Sets,Union Find,我来自一个命令式的背景,正在尝试实现一个简单的不相交集(“联合查找”)数据结构,以获得在Haskell中创建和修改(持久性)数据结构的一些实践。目标是有一个简单的实现,但我也关心效率,我的问题与此相关 首先,我使用union by rank创建了一个不相交的集合林实现,并从定义“点”的数据类型开始: data Point = Point { _value :: Int , _parent :: Maybe Point , _rank :: Int } deriving Sh

我来自一个命令式的背景,正在尝试实现一个简单的不相交集(“联合查找”)数据结构,以获得在Haskell中创建和修改(持久性)数据结构的一些实践。目标是有一个简单的实现,但我也关心效率,我的问题与此相关

首先,我使用union by rank创建了一个不相交的集合林实现,并从定义“点”的数据类型开始:

data Point = Point
  { _value  :: Int
  , _parent :: Maybe Point
  , _rank   :: Int
  } deriving Show
不相交的集合林是具有
Int的
IntMap
→ 点
映射:

type DSForest = IntMap Point

empty :: DSForest
empty = I.empty
单例集只是从其值x到值为x、无父项且秩为1的点的映射:

makeSet :: DSForest -> Int -> DSForest
makeSet dsf x = I.insert x (Point x Nothing 0) dsf
现在,有趣的部分是union。此操作将通过将另一个点设置为其父点(在某些情况下更改其秩)来修改点。如果
s'的秩不同,
只需“更新”(创建一个新点)即可使其父点指向另一个点。在它们相等的情况下,将创建一个新的
,其秩增加一:

union :: DSForest -> Int -> Int -> DSForest
union dsf x y | x == y = dsf 
union dsf x y = 
   if _value x' == _value y'
      then dsf 
      else case compare (_rank x') (_rank y') of
                  GT -> I.insert (_value y') y'{ _parent = Just x' } dsf 
                  LT -> I.insert (_value x') x'{ _parent = Just y' } dsf 
                            -- 1) increase x's rank by one:
                  EQ -> let x''  = x'{ _rank = _rank x' + 1 } 
                            -- 2) update the value for x's rank to point to the new x:
                            dsf' = I.insert (_value x'') x'' dsf 
                            -- 3) then update y to have the new x as its parent:
                         in I.insert (_value y') y'{ _parent = Just x'' } dsf'
  where x' = dsf ! findSet dsf x
        y' = dsf ! findSet dsf y
现在,对于我真正的问题,如果在
EQ
案例中,我做了以下操作:

EQ -> let dsf' = I.insert (_value x') x'{ _rank = _rank x' + 1} dsf 
       in I.insert (_value y') y'{ _parent = Just x'{ _rank = _rank x' + 1 }} dsf'
也就是说,首先插入一个新的
x,其秩增加,然后让
y'
的父项成为一个新的
x,其秩增加,这是否意味着它们不再指向内存中相同的
(这有关系吗?在使用/创建持久数据结构时,我是否应该担心这些问题?)

为了完整起见,这里是
findSet

findSet :: DSForest -> Int -> Int
findSet dsf' x' = case _parent (dsf' ! x') of
                     Just (Point v _ _)  -> findSet dsf' v
                     Nothing             -> x'
(也欢迎对本规范的效率和设计提出一般性意见。)

这是否意味着它们不再指向记忆中的同一点

我认为您不应该关心这个问题,因为这只是针对不可变值的运行时系统(也称为Haskell的RTS)的一个实现细节

至于其他建议,我想说让函数
findSet
返回
点本身,而不是键,因为这样可以消除在
union
中的查找

findSet :: DSForest -> Int -> Point
findSet dsf' x' = case _parent pt of
                     Just (Point v _ _)  -> findSet dsf' v
                     Nothing             -> pt
                  where
                      pt = (dsf' ! x')
union
函数中进行适当的更改。

共享是一个编译器的事情。当它识别公共子表达式时,编译器可能会选择用内存中的同一个对象来表示它们。但是,即使您使用这样的编译器开关(如
-fno cse
),它也没有义务这样做,而且两者可能是相同的(在没有开关的情况下,通常是)由内存中两个不同但值相等的对象表示。Re:

当我们为某个对象命名并使用该名称两次时,我们(合理地)希望它在内存中表示同一对象。但是编译器可能会选择复制它,并在两个不同的使用站点中使用两个单独的副本,尽管不知道是否会这样做。但是可能。Re:

另见:


这里有几个列表生成函数的示例,来自上面的最后一个链接。它们依赖于编译器,不复制任何内容,即按照需要调用lambda演算操作语义的预期共享任何命名对象(如nponeccop在注释中解释的),并且不单独引入任何额外共享以消除公共子表达式:

  • 共享固定点组合器,创建循环:

    修复f=x,其中x=fx

  • 非共享不动点组合器,创建伸缩多级链(即正则递归链)

    \u Y f=f(\u Y f)

  • 两级组合-回路和馈电

    \u 2 f=f(固定f)


  • 第一点意见:不相交集联合查找数据结构很难用纯函数的方式很好地完成。如果您只是想练习持久数据结构,我强烈建议您从简单的结构开始,如二进制搜索树

    现在,考虑到一个问题,考虑一下你的FINSET函数。它不实现路径压缩,也就是说,它不会使所有的节点沿着根点的路径直接到达根目录。要做到这一点,你需要更新DS森林中的所有这些点,这样你的函数就会返回(int,DS森林)或者(点,DS森林)。。在monad中执行此操作以处理传递DSForest的所有管道比手动传递该林更容易

    但现在是第二个问题。假设您如前所述修改findSet。它仍然不能完全满足您的需要。特别是,假设您有一个链,其中2是1的子级,3是2的子级,4是3的子级。现在您在3上执行findSet。这将更新3的点,使其父级是1而不是2。但4的父级仍然是旧的3点,其父点是2。这可能没什么大不了的,因为看起来你从来没有对父点做过任何事情,除了拉出它的值(在findSet中)。但是你从来没有对父点做过任何事情,除了拉出它的值,这一事实告诉我,它应该是一个可能整数,而不是一个可能点

    让我重复并扩展我在开始时所说的内容。不相交集是一种特别难以用功能性/持久性方式处理的数据结构,因此我强烈建议从更简单的树结构开始,如二元搜索树或左撇子堆,甚至抽象语法树。这些结构具有所有访问都可以访问的属性通过根——也就是说,您总是从根开始,沿着树向下走,以到达正确的位置。此属性使持久数据结构的标志性共享变得更加容易

    不相交集数据结构没有该属性。而不是始终从根开始