在具有 O(1) 元素访问的 Haskell 中实现高效的拉链式数据结构
Posted
技术标签:
【中文标题】在具有 O(1) 元素访问的 Haskell 中实现高效的拉链式数据结构【英文标题】:Implementing efficient zipper like data structure in Haskell with O(1) elements access 【发布时间】:2013-11-25 03:40:09 【问题描述】:问题
我想创建一个数据类型,它允许快速访问和修改其元素。是否可以在 Haskell 中创建一个结构和函数,其执行速度与简单的 C++ 实现一样快?
问题详情
我正在用 Haskell 编写一个编译器。我有 AST 由一个数据类型表示,让我们考虑以下一个:
import Prelude hiding (id)
-- this is a sample data type, the real one has got a lot of constructors
data AST = A id :: Int, x :: AST, y :: AST, z :: AST
| B id :: Int
| C id :: Int, x :: AST, y :: AST
| D id :: Int, u :: AST, v :: AST, w :: AST
每个 AST 节点都有一个唯一标识符。我很想在 Haskell 中实现以下功能:
一个函数getById
,它将以O(1)
的时间复杂度返回一个选定ID的AST节点。
能够在结构上创建“焦点”并相互独立地修改焦点元素。所以我希望能够记住一些子树的焦点,并能够以O(1)
时间复杂度修改每个这样的焦点。
我在考虑Zippers,但是他们有3个问题:
-
(据我所知)它们与简单的数据类型一起使用,例如二叉树,我们可以说,我们选择“左”或“右”分支。是否有任何简单的方法可以在上述复杂数据类型上使用它们?
我认为他们不会允许我以
O(1)
时间复杂度实现函数 getById
,对吗?
我认为使用 Zippers 创建一些独立的焦点是不可能的。我所说的独立焦点是指焦点,这将允许我们修改数据类型的不同部分,而无需重新计算其他焦点(在 O(1)
中)。
C++ 的思维方式
在 C++ 中,我们将能够创建指向 AST 节点 nodePtrs
的指针数组。函数nodeById
将在O(1)
中执行,只需访问*(nodePtrs[id])
。因为 C++ 结构是可变的,所以我们可以在 O(1)
中不受任何限制地修改它的元素。
【问题讨论】:
变异在被控制之前还不错。 Haskell 有许多提供可变数据结构的包,例如MVector
您应该尝试进入 Haskell 思维模式。改变你的 AST 不是 Haskell 的方式。
@Ankur:我更愿意按照 augustss 的建议使用功能性方式。可变方式是唯一获得与 C++ 相同速度的方式吗?
@augustss:我也有这种感觉,但是如何才能用不可变的数据获得同样的速度呢?
@danilo2 你是用 Haskell 的方式做的,发现太慢了吗?
【参考方案1】:
我认为拉链实际上总是可以实现的,你听说过differentiation吗?
好吧,关于getById
,我不确定这是个好主意。您可能想要一个像 getById :: Int -> IO AST
这样的 Haskell 函数,它在数组或其他东西中使用查找。但是由于您以后希望能够修改值(至少在概念上),您希望getById
返回新修改的 AST 值还是存储的第一个 AST 值?这一切都变得有问题。看看你是否可以删除你的 haskell 版本的 ID 可能是个好主意。
我认为你的关注点听起来可行。如果我们说ZAST
是 AST 的拉链数据类型。那么你也许可以有类似的东西。
makeFocus :: ZAST -> Focus ZAST
type Focus =
(ZAST -> ZAST) -> -- The modifier of the "below part"
ZAST -> -- The new "above part", you have to provide it again as it might have changed
ZAST -- The Result
但是是的,它并不像 C++ 方式那样方便。
总之,我认为您应该退后一步,看看您实际尝试做的事情(AST 优化、发射程序集等)是否可以使用不可变数据结构有效地完成。而不是固执地尝试在 Haskell 中实现可变 C++ 数据结构的相同规范。
【讨论】:
以上是关于在具有 O(1) 元素访问的 Haskell 中实现高效的拉链式数据结构的主要内容,如果未能解决你的问题,请参考以下文章