Оптимизировать список текстовых добавлений и удалений - PullRequest
9 голосов
/ 16 января 2010

У меня есть список, содержащий позиции текстовых добавлений и удалений, например:

     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 ]

3 голосов
/ 16 января 2010

Не решение, просто некоторые мысли:

  • Правило 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) - в основном, пузырьковая сортировка с дополнительными условиями остановки. Я не уверен, что смогу преодолеть эту сложность, поскольку стандартные алгоритмы здесь бесполезны из-за необходимости корректировать положение.

Однако вы можете значительно улучшить ситуацию, например, например, вам не нужен "полный сортировка"

2 голосов
/ 17 января 2010

Создайте двоичное дерево, представляющее документ до и после применения всех изменений. Каждый узел представляет собой либо оригинальный текст, либо вставленный / удаленный текст; последний тип узла включает в себя как количество исходного текста для удаления (возможно, 0), так и строку текста для вставки (возможно, пустую).

Первоначально дерево имеет только один узел: «0 до конца: исходный текст». Примените все изменения к нему, объединяя изменения, когда вы идете, где это возможно. Затем пройдитесь по дереву от начала до конца, испуская последний набор правок. Это гарантированно даст оптимальный результат.

  • Применение вставки: найдите соответствующую точку в дереве. Если он находится в середине или рядом с вставленным текстом, просто измените строку текста для вставки этого узла. В противном случае добавьте узел.

  • Применение удаления: найдите начальную и конечную точки в дереве - в отличие от вставки, удаление может охватывать весь диапазон существующих узлов. Измените начальный и конечный узлы соответственно и уничтожьте все промежуточные узлы. После того, как вы закончите, проверьте, есть ли у вас смежные узлы «вставленный / удаленный текст». Если это так, присоединяйтесь к ним.

Единственный хитрый момент - убедиться, что вы можете найти точки в дереве, не обновляя все дерево каждый раз, когда вы вносите изменения. Это делается путем кэширования на каждом узле общего объема текста, представленного этим поддеревом. Затем, когда вы вносите изменения, вам нужно только обновить эти кэшированные значения на узлах, непосредственно выше узлов, которые вы изменили.

Это выглядит строго O (n log n) для всех входных данных, если вы хотите реализовать сбалансированное дерево и использовать веревки для вставленного текста. Если вы отказываетесь от идеи всего дерева и используете векторы и строки, это O (n 2 ), но на практике может хорошо работать.

Рабочий пример. Вот как этот алгоритм будет применяться к вашему примеру, шаг за шагом. Вместо того, чтобы делать сложные ascii art, я поверну дерево на бок, покажу узлы по порядку и покажу структуру дерева по отступам. Надеюсь, это понятно.

Исходное состояние:

*: orig

Я сказал выше, что мы будем кэшировать количество текста в каждом поддереве. Здесь я просто поставил * для количества байтов, потому что этот узел содержит весь документ, и мы не знаем, как долго это будет. Вы можете использовать любое достаточно большое число, например, 0x4000000000000000L.

После вставки «ab» в положение 2:

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

После вставки «cde» в положение 1:

        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 строк кода.)

После удаления 1 символа в позиции 4 вы получите это ...

    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
1 голос
/ 17 января 2010

Я считаю, что это можно сделать значительно быстрее, чем O (n²) в среднем (вполне вероятно, что входные данные могут быть спроектированы так, чтобы не допустить быстрого анализа).Вы можете рассматривать последовательные добавления или удаления как наборы.Вы можете анализировать одну операцию за раз, и вам придется выполнить несколько условных преобразований:

  • Если добавление следует за добавлением или рядом дополнений, оно может
    • коснуться(один или несколько из) предыдущих дополнений: затем вы можете объединить эти дополнения
    • не трогать: вы можете заказать их (вам придется регулировать позиции)
  • Если удаление следует за добавлением или рядом дополнений, оно может
    • удалять только символы из добавления: тогда вы можете изменить добавление (если оно не разделит добавление)
    • удаляет только символы, не входящие в набор дополнений: тогда вы можете переместить удаление на позицию перед набором дополнений и, возможно, объединить дополнения;после этого набор удалений перед текущим набором дополнений, возможно, придется применить к дополнениям до того, как
    • сделает оба: затем вы можете сначала разбить его на два (или более) удаления и применить соответствующиеметод
  • Если удаление следует за удалением или рядом удалений, оно может:
    • коснуться (одного или нескольких из) предыдущего удаления (я): затем, вы можете объединить эти удаления
    • , не трогая: вы можете заказать их (вам придется корректировать позиции
    • в любом случае, затем вам придется применить анализ вновь сформированных удалений кпредыдущие добавления
  • Если после удаления добавление не требуется, на этом этапе преобразование не требуется

Это только первый черновик. Некоторые вещи могут потребоватьсясделать иначе, например, может быть проще или эффективнее всегда применять все удаления, так что результатом всегда будет только один набор удалений, за которым следует один набор дополнений.

1 голос
/ 16 января 2010

Инструменты "diff", используемые в системах управления исходным кодом, используют алгоритмы, которые производят минимальное редактирование, необходимое для преобразования одного фрагмента исходного кода в другой - возможно, стоит изучить их. Я думаю, что большинство из них (в конечном итоге) основаны на этом алгоритме , но прошло некоторое время с тех пор, как я прочитал эту тему.

0 голосов
/ 19 января 2010

Как уменьшить количество действий: алгоритмический подход может попытаться отсортировать действия. Я думаю, что после сортировки:

  1. Вероятность объединения соседних действий (как показали Сванте и Петерчен), будет расти.
  2. Это может привести к минимальному количеству действий, которые необходимо выполнить?

В следующем «номер позиции» обозначает позицию вставки или удаления текста.

Предполагая, что можно поменять местами два соседних действия (путем настройки номеров позиций и свойство 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

Как показывает первый пример, в некоторых случаях своп вызывает удаление удаления. Это благотворный побочный эффект. Это также вопрос, почему я предлагаю перенести все удаления в передний.

Надеюсь, я что-то не забыл и учел все обстоятельства.

0 голосов
/ 18 января 2010

Для простоты предположим, что в ваших текстах появляются только буквы a-z.

Инициализируйте список A со значениями a [i] = i для i = 1 до N (вы сами поймете, насколько большим должно быть N).

Выполните (смоделируйте) все свои операции на A. После этого проанализируйте A, чтобы найти необходимые операции:

Сначала найти требуемые операции удаления, найдя пропущенные числа в 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

Шаг 3 (-: 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. Помните, что после каждой операции удаления все индексы уменьшаются, вам необходимо сохранить общую сумму прежних операций удаления, чтобы рассчитать индекс текущей операции удаления.)

Теперь мы ищем последовательности символов, чтобы найти операции вставки. В нашем примере есть только одна последовательность: cdeab с индексом 1, поэтому у нас есть одна операция вставки: +: 1: cdeab

Надеюсь, это достаточно ясно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...