flcs
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>flcs-js</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="flcs.css">
</head>
<body>
<div class="container">
<div>
<form id="main" class="input-group">
<span class="input-group-btn">
<button id="rand" class="btn" type="button">Rand</button>
</span>
<input id="input" class="form-control" type="text">
<span class="input-group-btn">
<button id="go" class="btn btn-primary" type="submit">Go</button>
</span>
</form>
</div>
<div id="output">
<table class="table table-bordered">
<tbody id="display"></tbody>
</table>
</div>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.3.3/d3.min.js"></script>
<script src="flcs.js"></script>
</body>
</html>
function sfunc(sol) {
return sol.s + 2*sol.u*sol.v/(1+sol.g) + sol.v*sol.v;
}
function intersect_wrap(a,b,match) {
return match?intersect_match(a,b):intersect_gap(a,b);
}
function intersect_match(a,b) {
var p=function(sol) {return 2*sol.u/(1+sol.g) + 2*sol.v;}
var q=function(sol) {return sol.s + 2*sol.u*sol.v/(1+sol.g) + sol.v*sol.v;}
var x=(q(b)-q(a)) / (p(a)-p(b));
return Math.floor(x);
}
function intersect_gap(a,b) {
var p=a.s-b.s;
var q=p*(2+a.g+b.g)+2*a.u-2*b.u;
var r=p*(1+a.g)*(1+b.g)+2*a.u*(1+b.g)-2*b.u*(1+a.g);
var x=(-q+Math.sqrt(q*q-4*p*r)) / 2*p;
return Math.floor(x);
}
function solution(gap) {
this.s=0;
this.t=Infinity;
this.u=0;
this.g=gap || 0;
this.v=0;
this.extend=function(match) {
var sol=$.extend({},this);
if (match) {
sol.t--;
sol.v++;
} else if (this.v==0) {
sol.t--;
sol.g++;
} else {
sol.s=sfunc(sol);
sol.u=sol.v;
sol.g=1;
sol.v=0;
}
return sol;
}
}
function range(x,a,b) { // assume a<b
if (x<=a) return -1;
if (b<=x) return +1;
return 0;
}
function insert(list,sol,match) {
if (list.length<=0) {
sol.t=Infinity;
return [sol];
}
var p,q;
var touch=false;
var pfunc=function(sol) {
return sfunc(match?sol:sol.extend(true));
}
var begin=function(i) {
return (--i>=0)?list[i].t:(-1);
}
for (var i=0;i<list.length;i++) {
var sfs=pfunc(sol);
var sfi=pfunc(list[i]);
if (sfs>=sfi) {
p=i-1;
q=i;
if (sfs==sfi) {
var sfse=pfunc(sol.extend(match));
var sfie=pfunc(list[i].extend(match));
if (sfse<=sfie) return list; // discard sol
touch=true;
q=i+1;
}
break;
}
}
for (;p>=0;p--) {
var t=intersect_wrap(sol,list[p],match);
var r=isNaN(t)?(+1):range(t,begin(p),list[p].t);
if (r<=0) touch=true;
if (r==0) list[p].t=t;
if (r>=0) break;
}
for (;q<list.length;q++) {
var t=intersect_wrap(sol,list[q],match);
var r=isNaN(t)?(+1):range(t,begin(q),list[q].t);
if (r>=0) touch=true;
if (r==0) sol.t=t; // otherwise, infinity by default.
if (r<=0) {
if (t>0) break;
touch=true;
}
}
return [].concat(
list.slice(0,p+1),
touch?sol:[],
list.slice(q)
);
}
function lattice(gap) {
this.match=false;
this.list=(gap!=undefined)?[new solution(gap)]:[];
this.flcs=0;
}
function go_flcs(x,y) {
var m=x.length;
var n=y.length;
var mat=[];
for (var i=0;i<=m;i++) mat[i]=[new lattice(i)];
for (var j=1;j<=n;j++) mat[0][j]=new lattice(j);
for (var i=1;i<=m;i++) {
for (var j=1;j<=n;j++) {
go_lattice(mat,i,j,x[i-1]==y[j-1]);
}
}
return mat;
}
function go_lattice(mat,i,j,match) {
mat[i][j]=new lattice;
mat[i][j].match=match;
var yipo=function(match) {
return function(k,sol) {
mat[i][j].list=insert(mat[i][j].list,sol.extend(match),match);
};
};
if (match) $.each(mat[i-1][j-1].list,yipo(true));
$.each(mat[i-1][j].list,yipo(false));
$.each(mat[i][j-1].list,yipo(false));
if (match) {
mat[i][j].flcs=sfunc(mat[i][j].list[0]);
} else {
mat[i][j].flcs=Math.max(mat[i-1][j].flcs,mat[i][j-1].flcs);
}
}
function display(mat) {
var tb=d3.select('#display').html(null);
var tr=tb.selectAll('tr').data(mat)
.enter().append('tr');
var td=tr.selectAll('td').data(function(d) {return d})
.enter().append('td').classed('match',function(d) {return d.match});
td.append('div').classed('flcs',true).text(function(d) {return d.flcs.toFixed(2)});
td.append('div').classed('list',true);
var vec=function(x) {return '('+x.join(',')+')'};
var inf=function(x) {return (x==Infinity)?'∞':x};
var sol=td.select('div.list').selectAll('div').data(function(d) {return d.list})
.enter().append('div').classed('sol',true)
.html(function(d) {return vec([sfunc(d).toFixed(2),inf(d.t)])+' '+[d.u,d.g,d.v].join('-')});
}
function go() {
var input=$('#input').val().split(' ');
if (input.length!=2) return;
var mat=go_flcs(input[0],input[1]);
display(mat);
}
$('#main').submit(function() {
go();
return false;
});
function rand_range(a,b) {
return a+Math.floor(Math.random()*(b-a));
}
function rand_str(set,len) {
var str='';
while (len--) str=str.concat(set.charAt(rand_range(0,set.length)));
return str;
}
$('#rand').click(function() {
var set='abc',a=12,b=16;
$('#input').val([
rand_str(set,rand_range(a,b)),
rand_str(set,rand_range(a,b))
].join(' '));
go();
});
.container {
max-width: 100%;
}
.container>div {
margin-top: 20px;
}
#display td {
padding: 6px;
}
.match {
background-color: hsl(90,60%,90%);
}
.sol {
font-size: 10px;
white-space: nowrap;
}