如何实现不变性

时间:2018-06-22 17:48:15

标签: javascript graph immutability immutable.js trie

我正在尝试了解trie等不可变性的实现方式,这与JS中的不可变性有关。我了解应该如何进行重要的结构共享。

我的问题是说你有一个像这样的图结构:

a -- b
     |
     c
     |
     d -- h
          |
     e -- i -- l
          |
     f -- j -- m
          |
     g -- k -- n

因此,您将x添加到系统中。我将尝试两种不同的方式:

a -- b
     |
     c
     |
     d -- h -- x
          |
     e -- i -- l
          |
     f -- j -- m
          |
     g -- k -- n

那只是被添加为一个叶子节点。

a -- b
     |
     c
     |
     d -- h
          |
          x
          |
     e -- i -- l
          |
     f -- j -- m
          |
     g -- k -- n

将其添加到路径的中间。

我想知道不变的数据结构将如何处理这两种情况。因此,从本质上讲,我们具有一个函数f : graph -> graph',可将图形更改为“新图形”,而在幕后,它仅需对数据结构进行少量调整。不确定其外观或工作方式。我的第一个解释尝试是这样的……

它始于包装对象,就像JS对象顶部的ImmutableJS的API层。

 --------------------------
|                          |
|    a -- b                |
|         |                |
|         c                |
|         |                |
|         d -- h           |
|              |           |
|         e -- i -- l      |
|              |           |
|         f -- j -- m      |
|              |           |
|         g -- k -- n      |
|                          |
 --------------------------

然后您进行更改,它会创建一个 new 包装器对象。

 --------------------------           --------------------------
|                          |         |                          |
|    a -- b                |         |                          |
|         |                |         |                          |
|         c                |         |                          |
|         |                |         |                          |
|         d -- h --------------------------------- x            |
|              |           |         |                          |
|         e -- i -- l      |         |                          |
|              |           |         |                          |
|         f -- j -- m      |         |                          |
|              |           |         |                          |
|         g -- k -- n      |         |                          |
|                          |         |                          |
 --------------------------           --------------------------

然后对于第二个示例同样如此:

 --------------------------           -------------------------- 
|                          |         |                          |
|    a -- b                |         |                          |
|         |                |         |                          |
|         c                |         |                          |
|         |                |         |                          |
|         d -- h           |         |                          |
|              |           |         |                          |
|              o --------------------------------- x            |
|              |           |         |                          |
|         e -- i -- l      |         |                          |
|              |           |         |                          |
|         f -- j -- m      |         |                          |
|              |           |         |                          |
|         g -- k -- n      |         |                          |
|                          |         |                          |
 --------------------------           --------------------------

这些框是您使用的API对象,其中的图形是普通的JS数据对象。

但是在这些示例中,原始图形结构被修改(在第一个示例中放置指向h的链接,在第二个示例中放置o占位符)。所以我想知道您将这些东西具体化为不变的情况。我对图形所做的每一次更改都希望“返回一个新对象”,但是在幕后却有最佳的结构共享。

谢谢您的帮助。

1 个答案:

答案 0 :(得分:7)

trie的情况下的示例不是不变性的通用解决方案,它只是在树中表示数组然后对持久树应用通用解决方案的一种方式。

以下是持久图的一般解决方案

  1. 脂肪节点
    每个节点都存储其更改的历史记录以及这些更改的时间戳。在特定时间点查找图形时,我们提供时间戳以获取当时的版本。它具有空间效率(仅存储新值),但是在这种情况下,访问时间会受到影响,这是因为需要对每个节点的修改数组(任意长度)进行额外搜索({Mulplicative slowdown)。
  2. 路径复制
    在这种情况下,我们创建一个保留所有子代的新节点,并为它的根路径中的每个节点创建一个新节点。在这种情况下,我们必须存储一个根数组。它的访问时间与原始图相同,只是它需要花费额外的时间是由于在根数组(Additive slowdown)上进行搜索。这就是trie示例中使用的内容。由于每次更改都会创建一组具有新根的新节点,这表示从新根到新节点的路径,因此空间效率低下。

  3. 修改框(Sleator,Tarjan等)
    这一个结合了Fat节点和Path复制。每个节点只能存储一个修改。如果我们尝试更新已修改的节点,那么我们将使用路径复制并尝试创建具有重复路径的重复节点。有趣的是,在创建新路径时,我们必须注意修改框。在新路径中,仅重复那些已经被修改的节点,否则仅更新那里的修改框。

注意:“路径复制和修改”框适用于树(或可能是DAG),而不是通用图。由于这两个过程都涉及从简化节点到根级联创建新节点。通用图没有根。因此,我们唯一可用的方法是用于普通图的胖节点。

参考:
 1. https://en.wikipedia.org/wiki/Persistent_data_structure
 2. https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-854j-advanced-algorithms-fall-2005/lecture-notes/persistent.pdf

脂肪节点

节点和图的以下结构应足够

Node ->
    var value;
    Node parent
    Node[] children
    Modification[] modifications
Modification ->
    Node node
    Date timestamp

Graph -> (Adjancency list)
    {
        'a': [b],
        'b': [c],
        'c': [d],
        'd': [h],
        'e': [i],
        'f': [j],
        'g': [k],
        'h': [d, i],
        'i': [e, j, l],
        'j': [f, i, k, m],
        'k': [g, j, n],
        'l': [i],
        'm': [j],
        'n': [k],
    }

胖节点案例1

Case 1

胖节点案例2

Case 2

路径复制

如果您的示例中的图是一个以节点a为根的树,则路径复制的工作方式与trie示例中所述的相同

在具有根数组的简单树节点之后就足够了

Node ->
    var value
    Node parent
    Node[] children

Graph ->
    roots: [
        {
            Node root1,
            Date timestamp
        },
        {
            Node root2,
            Date timestamp
        }
        ...
    ]

由于修改了节点h,因此将复制从节点h到根节点a的整个路径。

路径复制案例1

Case 1

路径复制案例2

Case 2

修改框

假设示例中的图形为树,则只需满足以下条件

Node ->
    var value
    Node parent
    Node[] children
    ModificationBox modBox

ModificationBox ->
    timestamp,
    Attribute {
        type: value/parent/children[i] etc (only one attribute)
        value: value of attribute
    }


Graph ->
    roots: [
        {
            Node root1,
            Date timestamp
        },
        {
            Node root2,
            Date timestamp
        }
        ...
    ]

修改框案例1

节点h未修改

Case 1

修改框案例2

在这种情况下,假设h已被修改

Case 2