UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

172 lines (144 loc) 4.84 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) * Fabian Jakobs (fjakobs) ************************************************************************ */ /** * Class to implement different edit distance ideas. * * <a href="http://en.wikipedia.org/wiki/Edit_distance">Edit distance on Wikipedia</a> * <a href="http://en.wikipedia.org/wiki/Levenshtein_distance">Levenshtein distance on Wikipedia</a> */ qx.Class.define("qx.util.EditDistance", { statics: { OPERATION_DELETE: 1, OPERATION_INSERT: 2, OPERATION_REPLACE: 3, /** * Returns a distant matrix following a concept * named Levenshtein distance for two data structures * * @param dataA {Array} incoming source data * @param dataB {Array} incoming target data * @return {Integer[][]} outgoing matrix */ __computeLevenshteinDistance(dataA, dataB) { // distance is dataA table with dataA.length+1 rows and dataB.length+1 columns var distance = []; // posA and posB are used to iterate over str1 and str2 var posA, posB, cost; for (posA = 0; posA <= dataA.length; posA++) { distance[posA] = []; distance[posA][0] = posA; } for (posB = 1; posB <= dataB.length; posB++) { distance[0][posB] = posB; } for (posA = 1; posA <= dataA.length; posA++) { for (posB = 1; posB <= dataB.length; posB++) { cost = dataA[posA - 1] === dataB[posB - 1] ? 0 : 1; if (distance[posA] === undefined) { distance[posA] = []; } distance[posA][posB] = Math.min( distance[posA - 1][posB] + 1, // deletion distance[posA][posB - 1] + 1, // insertion distance[posA - 1][posB - 1] + cost // substitution ); } } return distance; }, /** * Computes the operations needed to transform dataA to dataB. * * @param distance {Integer[][]} Precomputed matrix for the data fields * @param dataA {Array} incoming source data * @param dataB {Array} incoming target data * @return {Map[]} Array of maps describing the operations needed */ __computeEditOperations(distance, dataA, dataB) { var operations = []; var posA = dataA.length; var posB = dataB.length; if (posA === 0) { // insert from begin to end // reverted order than in all other cases for optimal performance for (var i = 0; i < posB; i++) { operations.push({ operation: this.OPERATION_INSERT, pos: i, old: null, value: dataB[i] }); } return operations; } if (posB === 0) { // remove from end to begin for (var i = posA - 1; i >= 0; i--) { operations.push({ operation: this.OPERATION_DELETE, pos: i, old: dataA[i], value: null }); } return operations; } while (posA !== 0 || posB !== 0) { if (posA != 0 && distance[posA][posB] == distance[posA - 1][posB] + 1) { operations.push({ operation: this.OPERATION_DELETE, pos: posA - 1, old: dataA[posA - 1], value: null }); posA -= 1; } else if ( posB != 0 && distance[posA][posB] == distance[posA][posB - 1] + 1 ) { operations.push({ operation: this.OPERATION_INSERT, pos: posA, old: null, value: dataB[posB - 1] }); posB -= 1; } else { if (dataA[posA - 1] !== dataB[posB - 1]) { operations.push({ operation: this.OPERATION_REPLACE, pos: posA - 1, old: dataA[posA - 1], value: dataB[posB - 1] }); } posA -= 1; posB -= 1; } } return operations; }, /** * Returns the operations needed to transform dataA to dataB. * * @param dataA {Array} incoming source data * @param dataB {Array} incoming target data * @return {Map[]} Array of maps describing the operations needed */ getEditOperations(dataA, dataB) { var distance = this.__computeLevenshteinDistance(dataA, dataB); var operations = this.__computeEditOperations(distance, dataA, dataB); return operations; } } });