本地编辑纯功能树

发布于 2024-12-29 03:43:41 字数 712 浏览 5 评论 0原文

让我们定义一棵树 T:

    A
   / \
  B   C
 / \
D   E

假设一个新节点被添加到 E,产生 T':

    A
   / \
  B   C
 / \
D   E
     \
      G

在可变语言中,这是一项简单的任务 - 只需更新 E 的子节点,我们就完成了。然而,在一个不可变的世界中,有必要首先知道到 E 的路径,然后从 E + 新子派生出 E',然后派生 B',最后派生 A' ( = T')。

这很麻烦;理想情况下,会有一些函数可以获取 E 和 G(可能还有 T)的值并生成 T',而不提供 E 的路径。

我看到有两种可能的方法来解决这个问题:

  • 父引用 - 这样,每个节点将能够导出其到根的路径。两个问题:创建两个相互引用的节点(即父节点<->子节点)是纯函数式语言中的一个问题(有简单的解决方案吗?);且每当 E ​​-> E' 已派生,所有 E' 的子级也需要重新派生,因为它们现在存储 E 的旧值而不是 E'。
  • zippers - 每个节点在创建时都存储一个从其父拉链派生的拉链。相互引用问题消失了,但是当 E -> E 时仍然如此。 E' 已导出,E' 的所有儿童拉链也需要导出,因为它们现在指向老树 E。

考虑到合理的性能,我想要的是否可能?非常感谢您的任何意见!

Let's define a tree T:

    A
   / \
  B   C
 / \
D   E

Let's say a new node is added to E, yielding T':

    A
   / \
  B   C
 / \
D   E
     \
      G

In a mutable language this is an easy task - just update E's children and we're done. However, in an immutable world it is necessary to first know the path to E, then derive E' from E + new child, then derive B' and then finally A' ( = T').

This is cumbersome; ideally, there would be some function that would take the values of E and G (and possibly T) and produce T', without supplying the path to E.

I see two possible ways to attack this problem:

  • parent references - this way, every node would be able to derive its path to the root. Two problems: creating two mutually referencing nodes (i.e. parent <-> child) is a problem in a purely functional language (any easy solutions?); and whenever E -> E' is derived, all of E''s children need to be newly derived as well, since they now store the old value of E instead of E'.
  • zippers - every node stores a zipper on creation, derived from its parent zipper. The mutually referencing problem disappears, but still, when E -> E' is derived, all of E''s childrens' zippers need to be derived as well, since they now point to the old tree E.

Is what I desire even possible with a reasonable performance in mind? Many thanks for any input!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

緦唸λ蓇 2025-01-05 03:43:41

另一种选择,
基于延迟替换。
如果它对性能至关重要并且需要对其进行大量更改,我建议对其进行基准测试。

我已经在 F# 中实现了它,但是我认为除了打印功能之外我没有使用任何“不纯粹”的东西。

这是一段文字墙,
基本原则是让树保持惰性,
通过替换返回节点的函数来替换节点。

诀窍是您需要某种方法来识别节点,这不是它自己的引用/名称,也不是通过值。
标识必须可复制到替换节点上
在本例中,我使用了 System.Object,因为它们在引用上是不同的。

type TreeNode<'a> = {
    getChildren: unit -> seq<TreeNode<'a>>;
    value: 'a;
    originalRefId: System.Object; //This is set when the onject is created,
                                  // so we can identify any nodes that are effectivly this one 
    }


let BasicTreeNode : 'a ->seq<TreeNode<'a>>-> TreeNode<'a>  = fun nodeValue -> fun children ->
    {value = nodeValue; originalRefId = System.Object(); getChildren = fun () -> children;}


let rec ReplacementNode : TreeNode<'a> -> TreeNode<'a> -> TreeNode<'a> -> TreeNode<'a>  = 
    fun nodeToReplace -> fun newNode -> fun baseNode ->
        if (System.Object.ReferenceEquals(baseNode.originalRefId, nodeToReplace.originalRefId)) then
            //If it has the same Oringal 
            newNode //replace the node
        else
            //Just another pass on node, tranform its children, keep orignial reference
            {value = baseNode.value; 
             originalRefId = baseNode.originalRefId;
             getChildren = fun () -> 
                baseNode.getChildren() |> Seq.map(ReplacementNode nodeToReplace newNode); }


type TreeType<'a> = {
    Print: unit -> unit; 
    ReplaceNode: TreeNode<'a> -> TreeNode<'a> -> TreeType<'a>;
    //Put all the other tree methods, like Traversals, searches etc in this type
    }

let rec Tree  = fun rootNode ->
    {
        Print = fun () -> 
            let rec printNode = fun node -> fun depth -> 
                printf "%s %A\n" (String.replicate depth " - ")  node.value
                for n in node.getChildren() do printNode n (depth + 1)
            printNode rootNode 0
            ;
        ReplaceNode = fun oldNode -> fun newNode ->
            Tree (ReplacementNode oldNode newNode rootNode)



    }

测试用例/示例:

let e = BasicTreeNode "E" Seq.empty
let d = BasicTreeNode "D" Seq.empty
let c = BasicTreeNode "C" Seq.empty
let b = BasicTreeNode "B" [d;e]
let a = BasicTreeNode "A" [b;c]
let originalTree = Tree a
printf "The Original Tree:\n"
originalTree.Print()

let g = BasicTreeNode "G" Seq.empty
let newE = BasicTreeNode "E" [g]

let newTree = originalTree.ReplaceNode e newE
printf "\n\nThe Tree with a Local Change: \n"
newTree.Print()

printf "\n\nThe Original Tree is Unchanged: \n"
originalTree.Print()


printf "\n\nThe Tree with a Second Local Change: \n"
let h = BasicTreeNode "H" Seq.empty
let newC = BasicTreeNode "C" [h]
let newTree2 = newTree.ReplaceNode c newC
newTree2.Print()



printf "\n\nTrying to Change a node that has been replaced doesn't work \n"
let i = BasicTreeNode "i" Seq.empty
let newnewC = BasicTreeNode "C" [h; i]
let newTree3 = newTree.ReplaceNode c newC //newTree.ReplaceNode newc newnewC would work
newTree3.Print()

我们在测试结束时看到,使用旧节点名称(/引用)来替换对象是行不通的。
可以选择创建具有另一个节点的引用 ID 的新类型:

//Like a basicTreeNode, but reuses an existing ID, so they can be replaced for oneanother
let EdittedTreeNode = fun orignalNode -> fun nodeValue -> fun children -> 
    {value = nodeValue; originalRefId = orignalNode.originalRefId; getChildren = fun () -> children;}

您还可以编辑 ReplacementNode 定义,以便它保留它所替换的节点的 ID。 (不仅返回 newNode,而是返回另一个具有 value 的新节点,以及 newNodegetChildren >,但是 nodetoReplaceoriginalRefId

Another Option,
based around doing a Lazy Replace.
If it is performance critical and will have alot of changes made to it, I would suggest benchmarking it.

I have implemented it in F#, however I don't think i've used anything "inpure" except for my printing function.

This is a bit of a wall of text,
The basic principle is to keep the tree Lazy,
replace the nodes, by replacing the function that returns the node.

The trick is you need some way to identify an node, that isn't it's own reference/name, and isn't by value.
The identification has to be duplicable onto the ReplacementNodes
In this case I have used System.Object's as they are referentially each distinct.

type TreeNode<'a> = {
    getChildren: unit -> seq<TreeNode<'a>>;
    value: 'a;
    originalRefId: System.Object; //This is set when the onject is created,
                                  // so we can identify any nodes that are effectivly this one 
    }


let BasicTreeNode : 'a ->seq<TreeNode<'a>>-> TreeNode<'a>  = fun nodeValue -> fun children ->
    {value = nodeValue; originalRefId = System.Object(); getChildren = fun () -> children;}


let rec ReplacementNode : TreeNode<'a> -> TreeNode<'a> -> TreeNode<'a> -> TreeNode<'a>  = 
    fun nodeToReplace -> fun newNode -> fun baseNode ->
        if (System.Object.ReferenceEquals(baseNode.originalRefId, nodeToReplace.originalRefId)) then
            //If it has the same Oringal 
            newNode //replace the node
        else
            //Just another pass on node, tranform its children, keep orignial reference
            {value = baseNode.value; 
             originalRefId = baseNode.originalRefId;
             getChildren = fun () -> 
                baseNode.getChildren() |> Seq.map(ReplacementNode nodeToReplace newNode); }


type TreeType<'a> = {
    Print: unit -> unit; 
    ReplaceNode: TreeNode<'a> -> TreeNode<'a> -> TreeType<'a>;
    //Put all the other tree methods, like Traversals, searches etc in this type
    }

let rec Tree  = fun rootNode ->
    {
        Print = fun () -> 
            let rec printNode = fun node -> fun depth -> 
                printf "%s %A\n" (String.replicate depth " - ")  node.value
                for n in node.getChildren() do printNode n (depth + 1)
            printNode rootNode 0
            ;
        ReplaceNode = fun oldNode -> fun newNode ->
            Tree (ReplacementNode oldNode newNode rootNode)



    }

Test Case/Example:

let e = BasicTreeNode "E" Seq.empty
let d = BasicTreeNode "D" Seq.empty
let c = BasicTreeNode "C" Seq.empty
let b = BasicTreeNode "B" [d;e]
let a = BasicTreeNode "A" [b;c]
let originalTree = Tree a
printf "The Original Tree:\n"
originalTree.Print()

let g = BasicTreeNode "G" Seq.empty
let newE = BasicTreeNode "E" [g]

let newTree = originalTree.ReplaceNode e newE
printf "\n\nThe Tree with a Local Change: \n"
newTree.Print()

printf "\n\nThe Original Tree is Unchanged: \n"
originalTree.Print()


printf "\n\nThe Tree with a Second Local Change: \n"
let h = BasicTreeNode "H" Seq.empty
let newC = BasicTreeNode "C" [h]
let newTree2 = newTree.ReplaceNode c newC
newTree2.Print()



printf "\n\nTrying to Change a node that has been replaced doesn't work \n"
let i = BasicTreeNode "i" Seq.empty
let newnewC = BasicTreeNode "C" [h; i]
let newTree3 = newTree.ReplaceNode c newC //newTree.ReplaceNode newc newnewC would work
newTree3.Print()

We saw at the end of the test that using an old node Name (/reference) for the object being replaced does not work.
There is the option of creating a new type that has the reference Id of another node:

//Like a basicTreeNode, but reuses an existing ID, so they can be replaced for oneanother
let EdittedTreeNode = fun orignalNode -> fun nodeValue -> fun children -> 
    {value = nodeValue; originalRefId = orignalNode.originalRefId; getChildren = fun () -> children;}

You could also edit the ReplacementNode definition, so that it preserves the ID, of the node it replaces. (by not just returning newNode, instead returning yet another new node that has the value, and getChildren of the newNode, but the originalRefId of the nodetoReplace)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文