手机腾讯网mt2.0(http://mt.tencent.com)终于发布了,这个版本的增量更新算法基于编辑距离计算,做到了字符级别的增量更新,比之前的chunk算法更加精确,减少了chunk算法带来的一些冗余字符的下载,下面我们就来看看mt是如何利用这个算法来实现增量更新的.
首先我们来看看什么是Levenshtein Distance (编辑距离),编辑距离即从一个字符串变换到另一个字符串所需要的最少变化操作步骤,是俄罗斯科学家Vladimir Levenshtein在1965年提出这个概念。
那如何计算编辑距离呢。通常我们修改一个字符串为另外一个字符串的时候有集中方法,删除,替换,插入,其他的字符就是不变了,按照编辑代价算,删除,替换,插入这几种操纵的代价是1,即修改一个字符,不变则是0,表示没有修改,即操作代价是0. 首先定义这样一个函数——edit(i, j),它表示第一个字符串的长度为i的子串到第二个字符串的长度为j的子串的编辑距离。
通过动态规划法(dp),我们很容易得到以下公司:
· if i == 0 且 j == 0,edit(i, j) = 0
· if i == 0 且 j > 0,edit(i, j) = j
· if i > 0 且j == 0,edit(i, j) = i
· if i ≥ 1 且 j ≥ 1 edit(i, j) = min{ edit(i-1, j) + 1, edit(i, j-1) + 1, edit(i-1, j-1) + f(i, j) },当第一个字符串的第i个字符不等于第二个字符串的第j个字符时,f(i, j) = 1;否则,f(i, j) = 0
· 我们可以用一个矩阵来纪录这一个过程,以以beauty和batyu为例:
这个矩阵的红色部分就是我们纪录edit(I,j)的编辑距离。我们再通过另外一个矩阵记录一下我们获取以上编辑距离表的每个步骤,0表示未修改, 1表示替换,2表示删除,3表示插入,我们得到以下矩阵。
通过这个矩阵,我们可以知道从batyu修改成beauty,要使代价最小,每一步所进行的操作.通过从矩阵的右下脚处,随着编辑步骤往左上脚遍历,我们就能得到从batyu编辑成beauty每一步进行的操作。如果当前是删除,则往纵坐标减1.如果是替换或者相等,则取对角点,如果是插入,则横坐标减1。以上面矩阵为例,最后一个是arr[6][5]=2说明是删除,纵坐标减1,下一步骤是arr[6][4]为0,说明不变,则横坐标,纵坐标都减1, 下一步为arr[5][3]=0,以此类推,得到如下步骤(绿色框字体)
于是我们得到从batyu 到beauty得修改代价最小得步骤是
0-3-0-3-0-0-2
即,第一位不变,第二位插入一个字符,从beauty里取第二个字符’2’,第三位不变,第四位取第四个字符’u’,第四第五位不变,那我们用一个数组这个操作:
[ [ 1, 0 ], 'e', [ 2, 0 ], 'u', [ 3, 0 ], [ 4, 0 ] ],其中数组表示不变,字符表示插入或者替换。
我们进一步压缩有顺序得数组得到:
[ [ 1, 1 ], 'e', [ 2, 1 ], 'u', [ 3, 2 ] ]
其中得数组表示,从第几个字符开始n个字符不变,这就是传说中得增量文件。
通过这个增量文件数组和源字符串batyu我们很容易得到新字符串:
beauty=”batyu”.substr(1-1,1)+’e’+”batyu”.substr(2-1,1)+’u’+”batyu”.substr(3-1,2);
这就是我们mt2.0得增量更新算法,啥也不说了,我们上代码:
<!-- lang: js -->
function lcsDiff(source,target){
var SAME= 0,REPLACE= 1,DELETE= 2,INSERT=3;
var sourceArr=source.split('');
//var sLength=sourceArr.length;
var targetArr=target.split('');
//var tLength=targetArr.length;
//编辑距离矩阵
var disMatrix=[];
//步骤矩阵
var stepMatrix=[];
//生成一个空矩阵,二维数组
for(var i=0;i<=sourceArr.length;i++){
disMatrix[i]=[];
stepMatrix[i]=[];
for(var j=0;j<=targetArr.length;j++){
disMatrix[i][j]=0;
stepMatrix[i][j]=0;
}
}
console.log(disMatrix);
console.log(stepMatrix);
for(var i=0;i<=sourceArr.length;i++){
for(var j=0;j<=targetArr.length;j++){
// console.log(i+" "+j);
//在第0步,由于都是空,所以是0
if(i==0&&j==0){
disMatrix[i][j]=0;
stepMatrix[i][j]=SAME;
}
else if(i==0&&j>0){
disMatrix[i][j]=j;
stepMatrix[i][j]=INSERT;
}
else if(j==0&&i>0){
disMatrix[i][j]=i;
stepMatrix[i][j]=DELETE;
}
else if(i>0&&j>0){
var sameStep=(sourceArr[i-1]===targetArr[j-1]),
delStep=disMatrix[i-1][j]+1,
insertStep=disMatrix[i][j-1]+1,
replaceStep=disMatrix[i-1][j-1]+(sameStep?0:1);
//console.log(i+' '+j+":"+replaceStep+' '+delStep+' '+insertStep+" v:"+sourceArr[i-1]+' '+targetArr[j-1]);
//console.log(i+' '+j+":"+replaceStep+' '+delStep+' '+insertStep);
disMatrix[i][j]=Math.min(replaceStep,delStep,insertStep);
var stepAct=disMatrix[i][j];
switch(stepAct){
case replaceStep:
stepMatrix[i][j]=sameStep?SAME:REPLACE;
break;
case insertStep:
stepMatrix[i][j]=INSERT;
break;
case delStep:
stepMatrix[i][j]=DELETE;
break;
}
// console.log(i+' '+j+":"+replaceStep+' '+delStep+' '+insertStep+' act :'+stepMatrix[i][j]);
}
}
}
console.log("disMatrix:");
console.log(disMatrix);
console.log("stepMatrix:");
console.log(stepMatrix);
var diff=[];
for(i=sourceArr.length,j=targetArr.length;i>0&&j>0;){
var step=stepMatrix[i][j];
console.log('=========================='+i+' '+j+':'+step);
switch(step){
case SAME:
diff[j-1]=[i,SAME];
i--;j--;
break;
case REPLACE:
diff[j-1]=targetArr[j-1];
i--;j--;
break;
case DELETE:
diff[j-1]=DELETE;
i--;
break;
case INSERT:
diff[j-1]=targetArr[j-1];
j--;
break;
}
}
console.log(diff);
var preItem,tempStr='',tempArr,reArr=[];
for(i=0;i<diff.length;i++){
var item=diff[i];
if(i==0){
if(typeof(item)=='string'){
tempStr=item;
}
else{
tempArr=item;
tempArr[1]=1;
}
//continue;
}
else{
if(typeof(item)=='string'){
tempStr=tempStr+item;
if(typeof(preItem)=='object'){
reArr.push(tempArr);
}
}
else{
if(typeof(preItem)=='string'){
tempArr=item;
tempArr[1]=tempArr[1]+1;
reArr.push(tempStr);
tempStr='';
}
else{
if(preItem[0]==(item[0]-1)){
tempArr[1]=tempArr[1]+1;
}
else{
reArr.push(tempArr);
tempArr=item;
tempArr[1]=tempArr[1]+1;
}
}
}
}
preItem=item;
}
if(typeof(preItem)=='string'){
reArr.push(tempStr);
}
else{
reArr.push(tempArr);
}
return reArr;
}
function mergeLcs(src,diff){
var strBuffer='';
for(var i=0;i<diff.length;i++){
var item=diff[i];
if(typeof(item)=='string'){
strBuffer=strBuffer+item;
}
else{
strBuffer=strBuffer+src.substr(item[0]-1,item[1]);
}
}
return strBuffer;
}
var src="batyu";
var target="beauty";
console.log(src+" >> "+target);
var diffArray=lcsDiff(src,target);
console.log(diffArray);
var merStr=mergeLcs(src,diffArray);
console.log(merStr==target);
console.log(merStr);