monaco-editor
Version:
A browser based code editor
99 lines (98 loc) • 4.54 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { OffsetRange } from '../../../core/offsetRange.js';
import { SequenceDiff, InfiniteTimeout, DiffAlgorithmResult } from './diffAlgorithm.js';
import { Array2D } from '../utils.js';
/**
* A O(MN) diffing algorithm that supports a score function.
* The algorithm can be improved by processing the 2d array diagonally.
*/
export class DynamicProgrammingDiffing {
compute(sequence1, sequence2, timeout = InfiniteTimeout.instance, equalityScore) {
if (sequence1.length === 0 || sequence2.length === 0) {
return DiffAlgorithmResult.trivial(sequence1, sequence2);
}
/**
* lcsLengths.get(i, j): Length of the longest common subsequence of sequence1.substring(0, i + 1) and sequence2.substring(0, j + 1).
*/
const lcsLengths = new Array2D(sequence1.length, sequence2.length);
const directions = new Array2D(sequence1.length, sequence2.length);
const lengths = new Array2D(sequence1.length, sequence2.length);
// ==== Initializing lcsLengths ====
for (let s1 = 0; s1 < sequence1.length; s1++) {
for (let s2 = 0; s2 < sequence2.length; s2++) {
if (!timeout.isValid()) {
return DiffAlgorithmResult.trivialTimedOut(sequence1, sequence2);
}
const horizontalLen = s1 === 0 ? 0 : lcsLengths.get(s1 - 1, s2);
const verticalLen = s2 === 0 ? 0 : lcsLengths.get(s1, s2 - 1);
let extendedSeqScore;
if (sequence1.getElement(s1) === sequence2.getElement(s2)) {
if (s1 === 0 || s2 === 0) {
extendedSeqScore = 0;
}
else {
extendedSeqScore = lcsLengths.get(s1 - 1, s2 - 1);
}
if (s1 > 0 && s2 > 0 && directions.get(s1 - 1, s2 - 1) === 3) {
// Prefer consecutive diagonals
extendedSeqScore += lengths.get(s1 - 1, s2 - 1);
}
extendedSeqScore += (equalityScore ? equalityScore(s1, s2) : 1);
}
else {
extendedSeqScore = -1;
}
const newValue = Math.max(horizontalLen, verticalLen, extendedSeqScore);
if (newValue === extendedSeqScore) {
// Prefer diagonals
const prevLen = s1 > 0 && s2 > 0 ? lengths.get(s1 - 1, s2 - 1) : 0;
lengths.set(s1, s2, prevLen + 1);
directions.set(s1, s2, 3);
}
else if (newValue === horizontalLen) {
lengths.set(s1, s2, 0);
directions.set(s1, s2, 1);
}
else if (newValue === verticalLen) {
lengths.set(s1, s2, 0);
directions.set(s1, s2, 2);
}
lcsLengths.set(s1, s2, newValue);
}
}
// ==== Backtracking ====
const result = [];
let lastAligningPosS1 = sequence1.length;
let lastAligningPosS2 = sequence2.length;
function reportDecreasingAligningPositions(s1, s2) {
if (s1 + 1 !== lastAligningPosS1 || s2 + 1 !== lastAligningPosS2) {
result.push(new SequenceDiff(new OffsetRange(s1 + 1, lastAligningPosS1), new OffsetRange(s2 + 1, lastAligningPosS2)));
}
lastAligningPosS1 = s1;
lastAligningPosS2 = s2;
}
let s1 = sequence1.length - 1;
let s2 = sequence2.length - 1;
while (s1 >= 0 && s2 >= 0) {
if (directions.get(s1, s2) === 3) {
reportDecreasingAligningPositions(s1, s2);
s1--;
s2--;
}
else {
if (directions.get(s1, s2) === 1) {
s1--;
}
else {
s2--;
}
}
}
reportDecreasingAligningPositions(-1, -1);
result.reverse();
return new DiffAlgorithmResult(result, false);
}
}