【问题标题】:How to create a diff of two complex data structures?如何创建两个复杂数据结构的差异?
【发布时间】:2015-12-26 12:18:34
【问题描述】:

问题说明:

我目前正在寻找一个优雅和/但有效的解决方案来解决我认为很常见的问题。考虑以下情况:

我定义了一个基于BTree 的文件格式,它的定义(以简化的方式)如下:

data FileTree = FileNode [Key] [FileOffset]
              | FileLeaf [Key] [Data]

实现了从文件读取并将其写入惰性数据结构并且工作正常。这将导致以下实例:

data MemTree = MemNode [Key] [MemTree]
             | MemLeaf [Key] [Data]

现在我的目标是拥有一个通用函数updateFile :: FilePath -> (MemTree -> MemTree) -> IO (),它将读取FileTree 并将其转换为MemTree,应用MemTree -> MemTree 函数并将更改写回树结构。问题是必须以某种方式保存 FileOffset。

我有两种方法来解决这个问题。它们都缺乏优雅和/或效率:

方法 1:扩展 MemTree 以包含偏移量

这种方法扩展了 MemTree 以包含偏移量:

data MemTree = MemNode [Key] [(MemTree, Maybe FileOffset)]
             | MemNode [Key] [Data]

然后读取函数将读取FileTree 并将FileOffsetMemTree 引用一起存储。写入将检查一个引用是否已经有一个关联的偏移量,如果有,它只是使用它。

优点:易于实现,无需开销即可找到偏移量

缺点:向负责将偏移量设置为Nothing

用户公开内部

方法 2:在二级结构中存储偏移量

解决此问题的另一种方法是读取FileTree 并创建一个StableName.Map 以保持FileOffsets。这样(如果我正确理解StableName 的语义)应该可以获取最终的MemTree 并在StableName.Map 中查找每个节点的StableName。如果有条目,则节点是干净的,不必再次写入。

优点:不会将内部结构暴露给用户

缺点:涉及在地图中查找的开销

结论

这是我能想到的两种方法。第一个应该更有效,第二个更悦目。我希望您的 cmets 了解我的想法,也许有人甚至有更好的方法?

[编辑] 合理

我正在寻找这样的解决方案有两个原因:

一方面,您应该尝试使用类型系统在错误出现之前对其进行处理。前面提到的用户当然是系统下一层的设计者(即我)。通过处理纯树表示,某些类型的错误将不会发生。对文件中树的所有更改都应该在一个地方。这应该使推理更容易。

另一方面,我可以实现 insert :: FilePath -> Key -> Value -> IO () 之类的东西并完成它。但是,当我通过更新树来保留(一种)日志时,我将失去一个非常好的特征。事务(即合并多个插入)只是在内存中处理同一棵树并将差异写回文件的问题。

【问题讨论】:

  • 为什么要将MemTree 类型暴露给用户?你不能保持内部隐私,并公开你想要的任何接口吗?
  • 不是一个真正合适的解决方案,但这个问题提示您对第一种方法的类型强制版本进行了一些修改,即用户必须处理的地方(但如果他们不这样做,则会出现编译器错误)。 Some rough code on hpaste 如果你好奇的话。不过最终走向了稍微不同的方向,所以在这种情况下可能对你没有帮助。
  • @AndrewC:也许你是对的。我想我正试图过度设计一个微不足道的问题。我希望有一个 cool 解决方案来隐藏每个节点的附加信息。像 State Monad 这样的某种方式在不需要时会“隐藏”状态。这样,类型系统就可以防止我犯愚蠢的错误。但如果没有办法实现这一点,我会选择 simple 的方式。
  • Monads 可以隐藏东西,因为它们使用标准接口(和语法)来完成所有底层工作。您可以使用 monad 隐藏附加信息,但也许这不是树的最佳接口!为什么不推出自己的界面来隐藏引擎盖下的管道?也许有一个公共MemTree 和私人MemTreeAnnotatedinsert 应该为 FileOffset 秘密添加 NothingleftSubTreerightSubTree 应该给你没有 FileOffset 注释的数据,但如果 FileOffset 在节点中计数,fmap 可以保留它。 delete 应该保留它。
  • @Florian:与State 工作方式最接近的等价物可能是要求用户构造一个具体的更新函数,拉链样式,而不是直接公开MemTree。如果用户有MemTree 构造函数,那么繁琐的结构相等检查是你能做的最好的。上面 hpaste 中的内容(肯定会被疯狂地过度设计)实际上相当于一个捷径,通过标记未更改的子树,因此不需要进一步检查。

标签: haskell data-structures


【解决方案1】:

我认为包Data.Generic.Diff 可能完全符合您的要求。它参考了某人的论文以了解其工作原理。

【讨论】:

    【解决方案2】:

    我是 Haskell 的新手,所以我不会展示代码,但希望我的解释可能对解决方案有所帮助。

    首先,为什么不只向用户公开 MemTree,因为这是他们将更新的内容,并且 FileTree 可以完全隐藏。这样,以后,如果您想将其更改为访问数据库,例如,用户不会看到任何差异。

    所以,既然 FileTree 是隐藏的,为什么不在你要更新的时候读入它,那么你就有了偏移量,所以更新,然后再次关闭文件。

    保留偏移量的一个问题是它会阻止另一个程序对文件进行任何更改,在您的情况下这可能没问题,但我认为一般来说这是一个糟糕的设计。

    我看到的主要变化是 MemTree 不应该是惰性的,因为文件不会保持打开状态。

    【讨论】:

      猜你喜欢
      • 2020-07-16
      • 2011-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-12
      相关资源
      最近更新 更多