优化文本添加和删除列表

时间:2010-01-16 14:32:34

标签: algorithm optimization text-processing

我有一个包含文本添加和删除位置的列表,如下所示:

     Type   Position   Text/Length
1.   +      2          ab          // 'ab' was added at position 2
2.   +      1          cde         // 'cde' was added at position 1
3.   -      4          1           // a character was deleted at position 4

为了更清楚,这就是这些操作的作用:

    1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
    ---------------------------------
    t | e | x | t |   |   |   |   |  
1.  t | a | b | e | x | t |   |   |  
2.  c | d | e | t | a | b | e | x | t
3.  c | d | e | a | b | e | x | t |

行动次数可以减少为:

     Type   Position   Text/Length
1.   -      1          1           // 't' was deleted at position 1
2.   +      1          cdeab       // 'cdeab' was added at position 1

或者:

     Type   Position   Text/Length
1.   +      1          cdeab       // 'cdeab' was added at position 1
2.   -      6          1           // 't' was deleted at position 6

这些操作将保存在我的数据库中,以便对此进行优化:如何减少为获得相同结果而要执行的操作数量?有没有比O(n * n)更快的方法?

请注意,这些操作是按时间顺序排列的,更改操作的顺序会产生另一种结果。

6 个答案:

答案 0 :(得分:3)

不是解决方案,只是一些想法:

  • 规则1:如果两个连续的操作没有重叠范围,则可以交换它们(调整位置)
  • 规则2:可以在同一位置连续两次插入或移除
  • 规则3:当插入物后面有一个完全包含在插入物中的移除物时,它们可以连接起来

我没有看到最短解决方案的直接算法。但是,使用规则1 + 2的启发式方法可能是:

  • 将操作“向上”移除,除非
    • 你违反了规则1
    • 在移除之前移动插入
    • 该位置小于前任
    • 的位置
  • 在同一位置加入连续插入/删除

应用于样本,这意味着:

 + 2 ab
 + 1 cde
 - 4 1

规则1(2x):

+ 2 ab
- 1 1   // position adjusted by -3
+ 1 cde

- 1 1  
+ 1 ab  // position adjusted
+ 1 cde

规则2:

- 1 1
+ 1 cdeab // watch correct order!

原始实现将是O(N * N) - 基本上是具有附加停止条件的冒泡排序。我不确定是否打算降低这种复杂性,因为由于必须调整位置,标准算法在这里没用。

然而,您可能能够显着改善 - 例如你不需要“完全排序”

答案 1 :(得分:2)

在应用所有更改之前和之后创建表示文档的二叉树。每个节点代表原始文本或插入/删除的文本;后一种节点包括要删除的原始文本量(可能为0)和要插入的文本字符串(可能为空)。

最初树只有一个节点,“0到结尾:原始文本”。应用所有更改,尽可能合并更改。然后从头到尾走树,发出最后一组编辑。这可以保证产生最佳效果。

  • 应用插入:在树中查找适当的点。如果它位于插入文本的中间或附近,只需更改该节点的文本到插入字符串即可。否则添加一个节点。

  • 应用删除:在树中查找起点和终点 - 与插入不同,删除可以覆盖整个范围的现有节点。相应地修改起始节点和结束节点,并终止其间的所有节点。完成后,检查是否有相邻的“插入/删除文本”节点。如果是,请加入他们。

唯一棘手的问题是确保您可以在树中找到点,而无需在每次更改时更新整个树。这是通过在每个节点缓存该子树表示的文本总量来完成的。然后,当您进行更改时,您只需直接在节点上更新这些缓存的值上面您更改的节点。

如果您打算实现平衡树并使用绳索插入文本,那么对于所有输入,这看起来严格为O(n log n)。如果你放弃了整个树的想法并使用向量和字符串,它就是O(n 2 ),但在实践中可能会正常工作。

工作示例。以下是此算法将如何逐步应用于您的示例。我不是做复杂的ascii艺术,而是将树转向侧面,按顺序显示节点,并通过缩进显示树形结构。我希望它很清楚。

初始状态:

*: orig

我上面说过,我们会缓存每个子树中的文本量。这里我只给了一个*作为字节数,因为这个节点包含整个文档,我们不知道它有多长。您可以使用任何足够大的数字,比如0x4000000000000000L。

在位置2插入“ab”后

    2: orig, 2 bytes
*: insert "ab", delete nothing
    *: orig, all the rest

在位置1插入“cde”后:

        1: orig, 1 byte
    5: insert "cde", delete nothing
        1: orig, 1 byte
*: insert "ab", delete nothing
    *: orig, all the rest

下一步是删除位置4处的字符。暂停此处以查看我们如何在树中找到位置4.

从根开始。查看第一个子节点:该子树包含5个字符。所以位置4必须在那里。移动到该节点。看看它的第一个子节点。这次它只包含1个字符。不在那里。这个编辑包含3个字符,所以它不在这里;它紧接着。移动到第二个子节点。 (该算法约为12行代码。)

在第4位删除1个字符后,你得到了这个......

    4: orig, 1 byte
        3: insert "cde", delete nothing
*: insert "ab", delete nothing
    *: orig, all the rest

...然后,注意两个相邻的插入节点,将它们合并。 (请注意,给定两个相邻节点,一个在树层次结构中始终位于另一个节点之上。将数据合并到该较高节点;然后删除较低节点并更新其间的缓存子树大小。)

    1: orig, 1 byte
*: insert "cdeab", delete nothing
    *: orig, all the rest

答案 2 :(得分:1)

源代码控制系统中使用的“diff”工具使用算法来生成将一个源代码转换为另一个源代码所需的最小编辑 - 可能值得研究它们。我认为他们中的大多数是基于this algorithm的(最终),但是我已经有一段时间阅读了这个主题。

答案 3 :(得分:1)

我相信这可以比O(n²)平均快得多(很可能设计输入不允许快速分析)。您可以将连续添加或删除视为集合。您可以一次分析一个操作,并且必须进行一些条件转换:

  • 如果添加或添加后添加,则可能
    • 触摸(一个或多个)之前的添加:然后,您可以联合这些添加
    • 没有触摸:你可以订购它们(你必须调整位置)
  • 如果在添加或一组添加之后删除,则可能
    • 仅删除添加中的字符:然后,您可以修改添加(除非它会拆分添加)
    • 只删除不是来自添加集的字符:然后,您可以将删除移动到添加集之前的位置,并且可能联合添加;之后,当前一组添加之前的删除集可能必须应用于之前的添加
    • 同时执行这两项操作:然后,您可以先将其拆分为两个(或更多)删除并应用相应的方法
  • 如果删除后删除或删除,则可以:
    • 触摸(一个或多个)之前的删除:然后,您可以联合这些删除
    • 没有触摸:你可以订购它们(你必须调整位置)
    • 在任何情况下,您都必须对之前添加的新形成的删除进行分析
  • 如果删除后添加,此时不需要转换

这只是第一次草稿。有些事情可能必须以不同的方式完成,例如,总是应用所有删除可能更容易或更有效,因此结果总是只有一组删除后跟一组添加。

答案 4 :(得分:0)

为简单起见,我们假设只有字母a-z出现在你的文本中。

为i = 1到N初始化一个值为a [i] = i的列表A(你将自己弄清楚N应该有多大)。

在A上执行(模拟)所有操作。在此分析A之后,找到所需的操作:

Fist通过在A中找到缺失的数字来找到所需的删除操作(它们将形成连续值的组,一个组代表一个删除操作)。

在此之后,您可以通过查找连续序列来找到所需的插入操作 字母(一个序列是一个插入操作)。

在你的例子中:

init A:

1 2 3 4 5 6 7 8 9 10

步骤1(+:2:ab):

1 a b 2 3 4 5 6 7 8 9 10

Step2(+:1:cde):

c d e 1 a b 2 3 4 5 6 7 8 9 10

Step3( - :4:1):

c d e a b 2 3 4 5 6 7 8 9 10

现在我们搜索缺失的数字以查找删除。在我们的例子中,只缺少一个数字(即数字1), 所以只需要1次删除,所以我们有一个删除操作: - :1:1 (通常可能会丢失更多数字,每个缺失数字序列都是一个删除操作。 例如,如果1,2,3,5,6,10都是缺失的数字,则有3个删除操作: - :1:3, - :2:2, - :5:1。请记住,每次删除操作后,所有索引都会减少,您必须存储以前删除操作的总和,以计算当前删除操作的索引。)

现在我们搜索字符序列以查找插入操作。在我们的示例中,只有一个序列: 索引为1的cdeab,所以我们有一个插入操作:+:1:cdeab

希望这很清楚。

答案 5 :(得分:0)

如何减少操作次数:算法方法可以尝试对操作进行排序。我想,排序之后:

  1. 邻近行动可以加入的机会(以Svante和peterchen的方式表现出来), 会上升。
  2. 这可能会导致必须执行的操作数量最少?
  3. 以下“position-number”代表文本插入或删除位置。

    假设可以交换两个相邻的动作(通过调整位置数和 这两个动作的text / length属性),我们可以将动作列表带到我们的任何命令 喜欢。我建议使用升序将删除操作带到操作列表的前面 位置编号。删除操作后,添加操作将按升序排序 位置编号。

    以下示例应说明,为什么我认为可以交换任何相邻的操作。

    交换以下行动:

      1. + 2 aaa -> taaaext
      2. - 3 1   -> taaext
    

    产生一个动作:

      1. + 2 aa  -> taaext
    

    交换以下行动:

      1. + 3 aaa -> teaaaxt
      2. + 1 bb  -> bbteaaaxt
    

    收益率:

      1. + 1 bb  -> bbtext
      2. + 5 aaa -> bbteaaaxt
    

    交换以下行动:

      1. + 1 bb  -> bbtext
      2. - 2 2   -> bext
    

    收益率:

      1. - 1 1   -> ext
      2. + 1 b   -> bext
    

    如第一个示例所示,在某些情况下,交换会导致删除删除。这是一个 有益于副作用。这也是我建议将所有删除移动到的原因 前面。

    我希望我不会忘记某些事情并考虑所有情况。