2010-01-16 3 views
9

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

 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)?

Обратите внимание, что эти действия являются хронологическими, изменение порядка действий даст другой результат.

+1

Это очень продуманный пример. В нем показаны многие аспекты, которые нужно использовать для уменьшения количества операций. – Svante

ответ

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

Тем не менее, возможно, вы сможете улучшить ситуацию, особенно - например. вам не нужен «полный сорт»

+0

Отлично! Я не мог подумать об этом, спасибо! После некоторого тестирования я решу, какой ответ принять:] – Harmen

1

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

+0

Я читал об этих алгоритмах [diff, Levenshtein], но я уверен, что различие между двумя гигантскими фрагментами текста медленнее, чем слияние нескольких действий, подобных упомянутым выше. – Harmen

+0

И часто они не дают минимального редактирования либо, что-то, что достаточно * * (tm). –

1

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

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

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

+0

Благодарим за это описание, это действительно полезно! В некоторых примерах я расскажу об этом; это может занять некоторое время, чтобы понять это ^^ – Harmen

2

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

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

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

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

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

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

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

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

*: 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 
+0

Не могли бы вы привести пример, это звучит неплохо, но я не понимаю ... – Harmen

+0

ОК, обновленный с помощью обработанного примера. Я поставил столько деталей, сколько мог. Короче говоря, вы хотите найти существующий код для сбалансированного дерева, добавить поле поддерева для каждого узла и обернуть его в класс, который сохраняет правильные поля поддерева и объединяет смежные вставные узлы. Это нетривиально. –

0

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

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

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

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

После этого вы можете найти необходимые операции вставки, найдя последовательности последовательных букв (одна последовательность - одна операция вставки).

В вашем примере:

INIT А:

Шаг 1 (+: 2: AB):

1 AB 2 3 4 5 6 7 8 9 10

Шаг2 (+: 1: CDE):

CDE 1 AB 2 3 4 5 6 7 8 9 10

Step3 (-: 4: 1):

г д а б 2 3 4 5 6 7 8 9 10

Сейчас мы ищем отсутствующие номера, чтобы найти удалений. В нашем примере отсутствует только одно число (а именно номер 1), , поэтому требуется только 1 удаление, поэтому у нас есть одна операция удаления: -: 1: 1 (В общем, может отсутствовать больше номеров, каждая последовательность отсутствующих числа - одна операция удаления. Например, если 1, 2, 3, 5, 6, 10 - все отсутствующие номера, то есть три операции удаления: -: 1: 3, -: 2: 2, -: 5: 1 Помните, что после каждой операции удаления все индексы уменьшаются, вам нужно сохранить общую сумму предыдущих операций удаления, чтобы вычислить индекс текущей операции удаления.)

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

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

+0

Другой очень другой подход, спасибо! Но это не достаточно быстро, когда действия выполняются на самых разных позициях, например. '+ 100000 a' и' - 2 1' - это можно сделать в O (1), тогда как вашему решению требуется 100000 символов, чтобы пройти три раза ... – Harmen

+0

Какова сложность вашего решения? можете ли вы дать более сложный пример того, как он работает?возможно: с более чем одной результирующей вставкой и удалением ... – WildWezyr

+0

@ Хармен Я только заявил, что для решения проблемы лучше всего выполнить операции, а затем прочитать результат. Я назвал структуру данных List просто для удобства аргумента, но я никогда не определял ее реализацию (LinkedList? TreeList? Пользовательская реализация, которая притворяется [i] = i для неинициализированных индексов?). Но вы не указали, насколько велик оригинальный текст, сколько операций ввода есть и что должно быть оптимизировано, поэтому я извиняюсь;) – witzar

0

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

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

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

Предполагая, что можно поменять местами две соседние действия (путем регулировки положения-номера и текст/свойство длины этих двух действий), мы можем привести остросюжетном список к любому заказу мы нравится. Я предлагаю привести действия удаления в начало списка действий с восходящими номерами позиций. После действий удаления операции добавления сортируются с восходящими позиционными числами.

Следующие примеры должны продемонстрировать, почему я считаю, что можно менять любые соседние действия.

Swaping следующие действия:

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

уступает одно действие:

1. + 2 aa -> taaext 

Swaping следующие действия:

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

уступает:

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

Swaping следующие действия:

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

уступает:

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

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

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

Смежные вопросы