本地编辑纯功能树
让我们定义一棵树 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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
另一种选择,
基于延迟替换。
如果它对性能至关重要并且需要对其进行大量更改,我建议对其进行基准测试。
我已经在 F# 中实现了它,但是我认为除了打印功能之外我没有使用任何“不纯粹”的东西。
这是一段文字墙,
基本原则是让树保持惰性,
通过替换返回节点的函数来替换节点。
诀窍是您需要某种方法来识别节点,这不是它自己的引用/名称,也不是通过值。
标识必须可复制到替换节点上
在本例中,我使用了 System.Object,因为它们在引用上是不同的。
测试用例/示例:
我们在测试结束时看到,使用旧节点名称(/引用)来替换对象是行不通的。
可以选择创建具有另一个节点的引用 ID 的新类型:
您还可以编辑
ReplacementNode
定义,以便它保留它所替换的节点的 ID。 (不仅返回newNode
,而是返回另一个具有value
的新节点,以及newNode
的getChildren
>,但是nodetoReplace
的originalRefId
)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.
Test Case/Example:
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:
You could also edit the
ReplacementNode
definition, so that it preserves the ID, of the node it replaces. (by not just returningnewNode
, instead returning yet another new node that has thevalue
, andgetChildren
of thenewNode
, but theoriginalRefId
of thenodetoReplace
)