Оптимизация diff
Оптимизация диффа
Это руководство показывает, как очистить необработанный diff — список DiffOperation значений (см Diff как они формируются) — в каноническую, минимальную форму, используя проходы постобработки в DiffOptimization подпространстве. Каждый проход реализует IDiffOptimizationOperation и изменяет список diff на месте, поэтому проходы можно цепочкой выполнять последовательно, постепенно упрощая результат.
Контракт оптимизации
IDiffOptimizationOperation определяет единственный метод, Execute(diffs), который нормализует изменяемый список значений DiffOperation на месте, сохраняя исходный и конечный тексты, которые представляет дифф. Каждый оптимизатор в этом подпакете реализует этот интерфейс, поэтому их можно вызывать взаимозаменяемо или комбинировать в конвейере.
List<DiffOperation> diffs = new List<DiffOperation>
{
new DiffOperation(Operation.Equal, "The quick "),
new DiffOperation(Operation.Delete, "brown "),
new DiffOperation(Operation.Insert, "red "),
new DiffOperation(Operation.Equal, "fox")
};
IDiffOptimizationOperation optimizer = new OperationsMerger(EditOperationsOrder.DeleteFirst);
optimizer.Execute(diffs);Объединение смежных операций
OperationsMerger объединяет дифф в его каноническую минимальную форму: он сводит вместе смежные последовательности одного типа операций, а для смешанной последовательности удалений/вставок выделяет общий префикс в предшествующее равенство и общий суффикс в последующее равенство.
var merger = new OperationsMerger(EditOperationsOrder.DeleteFirst);
merger.Execute(diffs);Устранение коротких равенств
MergingOptimizer выполняет семантический проход очистки: он удаляет равенства, не превышающие по размеру окружающие их правки, сворачивая эти короткие общие участки обратно в соседние delete/insert, а затем повторно объединяет результат в каноническую форму.
var semanticOptimizer = new MergingOptimizer(EditOperationsOrder.InsertFirst);
semanticOptimizer.Execute(diffs);Перемещение правок через равенства
OperationsSlideMerger сдвигает отдельную правку, окружённую с обеих сторон равенствами, в сторону, устраняя одно из этих равенств и дальше канонизируя diff.
IDiffOptimizationOperation slideMerger = new OperationsSlideMerger();
slideMerger.Execute(diffs);Контроль порядка delete/insert
Когда для одной и той же позиции выдаются операции delete и insert, перечисление EditOperationsOrder определяет, какую из них оптимизатор помещает первой в результирующей последовательности операций: DeleteFirst или InsertFirst.
var order = EditOperationsOrder.DeleteFirst;
var optimizer = new MergingOptimizer(order);
optimizer.Execute(diffs);Советы и лучшие практики
- Запустите
OperationsMergerсначала, чтобы объединить необработанные правки перед применением семантических проходов —MergingOptimizerиOperationsSlideMergerоба предполагают, что соседние операции одного типа уже объединены. - Выберите одно значение
EditOperationsOrderи используйте его последовательно во всех оптимизаторах конвейера; смешиваниеDeleteFirstиInsertFirstмежду проходами может отменить порядок, установленный предыдущим проходом. - Все три оптимизатора изменяют
List<DiffOperation>на месте — сначала клонируйте список, если вам нужно сохранить не оптимизированный diff для сравнения. - Поскольку каждый оптимизатор реализует
IDiffOptimizationOperation, вы можете хранить конвейер какIDiffOptimizationOperation[]и вызыватьExecuteдля каждого в цикле, вместо того чтобы жёстко задавать каждый проход по имени. - Применяйте
OperationsSlideMergerпосле проходов слияния — скольжение правки имеет смысл только после того, как соседние блоки одного типа уже объединены.
Распространённые проблемы
| Проблема | Причина | Исправление |
|---|---|---|
| Diff по-прежнему содержит крошечные фрагментированные равенства | Был выполнен только OperationsMerger; короткие равенства между правками никогда не сворачивались | Также запустите MergingOptimizer после OperationsMerger |
| Порядок удаления/вставки неожиданно меняется между проходами | Разные значения EditOperationsOrder были переданы разным оптимизаторам | Используйте одинаковое значение EditOperationsOrder для каждого оптимизатора в конвейере |
| Похоже, оптимизатор не оказывает эффекта | Список diff уже находился в канонической форме, либо копия списка была оптимизирована вместо оригинального списка | Подтвердите, что вы передаёте тот же List<DiffOperation>-референс, который читается нижестоящим кодом |
FAQ
Что означает «каноническая форма» для diff?
Это означает, что соседние операции одного типа были объединены, а любой общий префикс/суффикс в смешанных блоках удаления/вставки был перенесён в соседнее равенство, так что последовательность не содержит избыточной фрагментации.
Обязательно ли использовать все три оптимизатора?
Нет. IDiffOptimizationOperation позволяет применять только необходимые проходы, но запуск OperationsMerger перед семантическими проходами (MergingOptimizer, OperationsSlideMerger) дает наиболее последовательные результаты.
В чём разница между OperationsMerger и MergingOptimizer?
OperationsMerger выполняет структурное объединение (последовательности одного типа, общий префикс/суффикс). MergingOptimizer идёт дальше и сворачивает короткие равенства, окружённые правками, обратно в соседнее удаление/вставку перед повторным слиянием.
Меняет ли EditOperationsOrder то, что представляет diff?
Нет. Он лишь контролирует порядок выдачи удаления и вставки, которые применяются к одной и той же позиции — результирующая реконструкция текста не изменяется.
API Reference Сводка
| Класс / Метод | Описание: |
|---|---|
IDiffOptimizationOperation | Интерфейс для постобработки, которая нормализует diff на месте |
IDiffOptimizationOperation.Execute(diffs) | Запускает проход оптимизации над изменяемым списком значений DiffOperation |
OperationsMerger | Объединяет соседние однотипные операции и выделяет общий префикс/суффикс в равенства |
MergingOptimizer | Удаляет короткие равенства, окружённые правками, затем повторно объединяет в каноническую форму |
OperationsSlideMerger | Сдвигает правку, окружённую равенствами, чтобы устранить одно из них |
EditOperationsOrder | Перечисление, контролирующее, будет ли DeleteFirst или InsertFirst выведено в общей позиции |