@qooxdoo/framework
Version:
The JS Framework for Coders
166 lines (133 loc) • 4.61 kB
JavaScript
/* ************************************************************************
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 : function(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 : function(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 : function(dataA, dataB)
{
var distance = this.__computeLevenshteinDistance(dataA, dataB);
var operations = this.__computeEditOperations(distance, dataA, dataB);
return operations;
}
}
});