UNPKG

monaco-editor

Version:
99 lines (98 loc) 4.54 kB
/*--------------------------------------------------------------------------------------------- * 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); } }