【问题标题】:Context sensitive diff implementation上下文敏感的差异实现
【发布时间】:2014-01-12 03:44:42
【问题描述】:

总结和基本问题

使用 MS Access 2010 和 VBA(叹息..)

我正在尝试实现一个专门的 Diff 函数,该函数能够根据已更改的内容以不同的方式输出更改列表。我需要能够生成一份简明的更改列表,以提交我们的记录。

我想使用诸如<span class="references">These are references 1, 6</span> 之类的html 标签,以便我可以使用代码查看更改并自定义更改文本的输出方式。或者其他任何可以完成我的任务的东西。

我认为这是一种提供可扩展方式来自定义输出的方法,并可能将事物转移到更强大的平台并实际使用 html/css。

有谁知道类似的项目可以为我指明正确的方向吗?

我的任务

我有一个带有工作操作指令表的访问数据库 - 通常有 200-300 个操作,其中许多操作从一个版本更改为另一个版本。我目前已经实现了一个函数,它遍历表,查找已更改的指令并进行比较。

请注意,每条操作指令通常是几句话,末尾有几行,并带有一些文档参考。

我的算法基于"An O(ND) Difference Algorithm and Its Variations",效果很好。

Access 支持“富”文本,它只是美化了简单的 html,因此我可以轻松地生成带有格式化添加和删除的全文,即添加像 <font color = "red"><strong><i>This text has been removed</i></strong></font> 这样的标签。 Diff 过程的主要输出是操作的全文,其中包括彼此内联的未更改、删除和插入的文本。 diff 过程添加了<del><ins> 标记,这些标记稍后会替换为格式化文本(结果类似于堆栈交换编辑的更改视图)。

但是,正如我所说,我需要以人类可读格式列出的更改。事实证明,这很困难,因为许多更改会产生歧义。

例如:如果一种化学品正在从“A类”更改为“C类”,那么容易生成的更改文本是“将'A'更改为'C'”,这对于审查更改的人。更常见的是末尾的文档引用:将 SOP 3 添加到列表中,例如“SOP 1, 2, 3”会生成文本“Add '3'”。显然也没有用。

最有用的是指定为“SOP”文本的文本的自定义输出,以便输出为“添加对 SOP 3 的引用”。

我从以下解决方案开始:

将单词组合在一起,例如将诸如“SOP 1、2、3”之类的文本作为一个标记进行比较。这会生成文本“将 'SOP 1, 2' 更改为 'SOP 1, 2, 3”。当列表很大并且您试图确定实际更改的内容时,这会变得混乱。

我现在在哪里

我现在尝试在运行 diff 算法之前添加额外的 html 标记。例如,我将通过“预处理器”运行文本,它将“SOP 1, 2”转换为 SOP 1, 2

一旦 Diff 过程返回完整的更改文本,我会扫描它并注意当前的“类”文本,当有 <del><ins> 时,我会捕获标签之间的文本并使用 SELECT CASE阻止类以解决每个更改。

这实际上在大多数情况下都可以,但是我必须解决许多问题,例如添加 Diff 决定最短路径是删除某些开始标签并插入其他标签。这将创建一个场景,即有两个 <span> 标签但只有一个 </span> 标签。

终极问题

我正在寻求建议,要么继续我已经开始的方向,要么尝试不同的方法,然后再投入更多时间来寻找次优的解决方案。

提前谢谢大家。

另请注意:

典型的运行时间大约是 1.5 到 2.5 秒,我会尝试更多花哨的东西和一堆 debug.prints。所以跑一两次额外的传球不会是杀手。

【问题讨论】:

    标签: html vba algorithm ms-access diff


    【解决方案1】:

    很明显,报告您所拥有的结构的最小变化方面的差异并不是您想要的。你想报告一些上下文。

    为此,您必须确定要报告的上下文,以便您可以决定其中的哪一部分是有趣的。你勾勒出一个想法,你将结构的某些元素融合在一起(例如,'SOP' '1' '2' into 'SOP 1 2'),但在我看来这走错了路。它所做的是改变最小结构元素的大小,而不是报告更好的上下文。

    虽然我不确定这是不是正确的方法,但我会尝试使用 语法 来描述您的结构,例如 BNF。例如,您可能拥有的一些语法规则是:

     action = 'SOP' sop_item_list ;
     sop_item_list = NATURAL ;
     sop_item_list = sop_item_list ',' NATURAL ;
    

    现在,一个实际的 SOP 项目可以表征为一个抽象语法树(显示嵌套的子节点,可通过常量索引以到达子树):

     t1: action('SOP',sop_item_list(sop_item_list(NATURAL[1]),',',NATURAL[2]))
    

    您仍然希望使用您建议的动态编程算法之类的方法来计算差异,但现在您需要一个最小的树增量。做对了(我的公司为传统计算机语言的语法制作工具,你可以找到公开可用的树差异算法),你可以得到如下的增量:

      replace t1[2] with op_item_list(sop_item_list(sop_item_list(NATURAL[1]),',',NATURAL[2]),',',NATURAL[3]
    

    本质上是通过将 (SOP,1,2) 粘合到单个项目中得到的,但没有您个人决定这样做的外部临时性。

    我认为,真正的价值在于您可以使用树 t1 来报告上下文。特别是,您从树的根开始,并打印子树的 summaries(显然您不想打印完整的子树,因为那样只会返回完整的原始文本)。 通过将子树打印到 1 或 2 个级别的深度并省略任何深度(例如,将列表表示为“...”,将单个子树表示为“_”),您可以打印如下内容:

    replace 'SOP' ... with 'SOP',...,3
    

    我认为这是您在具体示例中寻找的内容。

    不,这不是算法;这是一个想法的草图。事实上,我们有计算有用 delta 的树-delta 算法,而总结的想法(坦率地说,来自 LISP 调试器)表明这可能会推广到有用的东西,或者至少会带你进入一个新的方向。

    在 AST 方面有您的答案,也应该可以相对容易地生成您想要的 HTML。 (人们一直在使用 XML,而 XML 基本上是一种树表示)。

    【讨论】:

    • (大声笑)谢谢!这是一个有趣的选择。这意味着要重写很多东西,但我会在接下来的几天里深入研究这个问题,如果它提供了最简洁的解决方案,那么你就有了。我之前实现了一个非常简单的编译器,并且由于我的学习而熟悉了 BNF。我从未使用过 lisp,尽管我知道它的嵌套结构会借给树。你能提供任何链接来指引我正确的方向吗?我看到很多关于 delta 调试的信息,但这似乎是在分析单个文件,而不是比较两个文件。再次感谢!
    • 您可能想要研究的是“树差分”算法。 (您似乎找到了字符串/行差异的经典方法。查看stackoverflow.com/questions/5894879/…
    • 再次感谢您的链接,我将能够从信息中学到很多东西。
    【解决方案2】:

    尝试根据 Prolog 样式的重写规则进行思考,将您的指令转换为规范形式,从而使 diff 算法产生您需要的内容。您指定的问题将通过此规则解决:

    SOP i1, i2, ... iN  ->  SOP j1, SOP j2, ... SOP jN  where j = sorted(i)
    

    换句话说,将 SOP“分配”到以下整数的排序列表上。这将诱使 diff 算法提供完全合格的更改报告“添加 SOP 3”。

    通过在输入中搜索左侧的匹配项并用相应的右侧替换来应用规则。

    您可能已经这样做了,但如果输入被标记化,您将获得更多常识性分析:“SOP”应该被视为 diff 算法的单个“字符”。如果空格和换行符很重要或被忽略,则可以将它们减少为用于空格和换行符的标记。

    您可以在字符级别执行另一种差异来测试标记的“模糊”相等性,以解决匹配左侧时的印刷错误。 “SIP”和“SOP”将被视为“匹配”,因为它们的编辑距离仅为 1(并且 I 和 O 在 QUERTY 键盘上仅相隔一个键!)。

    如果您考虑到您现在得到的输出中的所有怪癖,并且可以将每个怪癖作为重写规则进行纠正,该规则将输入带到 diff 算法产生您需要的形式的形式,那么剩下的就是实现重写系统。以一种有效的通用方式执行此操作,以便更改和添加规则不涉及大量的 ad hoc 编码,这是一个难题,但已经过研究。有趣的是,@Ira Baxter 提到了 lisp,因为它非常适合作为这类事情的工具。

    Ira 对语法树的建议自然属于重写规则方法。例如,假设 SOP 有部分和段落:

    SOP 1 section 3, paras 2,1,3
    

    是一个层次结构,应该重写为

    SOP 1 section 3 para 1, SOP 1 section 3 para 2, SOP 1 section 3 para 3
    

    重写规则

    paras i1, i2, ... iN  ->  para j1, para j2, ... para jN  where j = sorted(i)
    section K para i1, ... para iN  ->s section K para j1, ... section K para j1
    SOP section K para i1, ... section K para i1 -> SOP section K para j1, ... SOP section K para j1 
    

    当应用三遍时,将产生类似“SOP 1 section 3, para 4 was added”的结果。

    虽然实现规则和重写的策略有很多,包括将每一个都编码为 VB 中的过程(啊……),但还有其他方法。 Prolog 是尽可能普遍地做到这一点的一次伟大尝试。 There is a free implementation.还有其他人。我使用TXL 来完成有用的重写。 TXL 的唯一问题是它假定您有一个相当严格的输入语法,这听起来不像您的问题。

    如果您在当前输出中发布更多怪癖示例,我可以跟进此内容,提供有关重写规则的更多详细信息。

    【讨论】:

    • 谢谢!虽然 Ruud 和 Baxter 提供了宝贵的反馈,但在比较之前以这种方式重写的想法对于完成我的具体问题至关重要。我一直在使用正则表达式,根据需要使用正则表达式重写会很容易。
    • 很高兴能提供帮助。只要您有时间通过​​每个正则表达式的数据(即数据足够小),这是一个很好的方法。更复杂的算法会对数据进行预处理,以便每个规则只需要扫描一小部分数据。
    【解决方案3】:

    如果你决定继续你已经取得的成就(IMO 你已经很远了),你可以考虑做两步差异。

    将单词组合在一起,例如将诸如“SOP 1,2,3”之类的文本作为一个标记进行比较。

    这是一个好的开始;您已经设法让用户清楚地了解上下文。

    这会生成文本“将 'SOP 1, 2' 更改为 'SOP 1, 2, 3'”。当列表很大并且您试图确定实际发生了什么变化时,这会变得杂乱无章。

    如何在找到的标记之间再做一次差异传递(即比较“SOP 1、2”和“SOP 1、2、3”),这次不使用单词分组,以生成额外的信息?这将使完整的输出如下所示:

    将“SOP 1、2”更改为“SOP 1、2、3”

    更改详细信息:添加“3”

    文本有点神秘,所以你可能想在那里做一些改进。我还建议在第一行中截断冗长的标记('SOP 1, 2, 3, ...'),因为第二行应该已经提供了足够的细节。

    我不确定第二次传递对性能的影响;在一个有很多变化的大文本中,您可能会遇到多次差异功能的往返。您可以通过将第一次传递的更改累积到一个“更改文档”中进行优化,在它上运行第二次传递,然后将结果拼接在一起。

    HTH。

    【讨论】:

    • 谢谢!我一直在玩类似的东西。获得最短更改(不尝试获取额外上下文)的基本差异似乎最适合直观地显示更改,我希望能够做到这一点。现在也许我可以返回并添加我的上下文,而不是在运行 diff 之前。
    • 这应该是可行的,尽管它可能会使用所有额外的差异将运行时间延长 3-5 倍,尽管这最终并不是那么糟糕。如果我一开始就正确设置了分组令牌的上下文,那么确定它的上下文并不难。
    猜你喜欢
    • 2015-12-09
    • 2015-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-12
    • 1970-01-01
    相关资源
    最近更新 更多