This repository was archived by the owner on Mar 22, 2019. It is now read-only.
forked from webpack/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlcs.js
174 lines (148 loc) · 5.72 KB
/
lcs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// Refer to http://www.xmailserver.org/diff2.pdf
// Longest Common Subsequence
// @param A - sequence of atoms - Array
// @param B - sequence of atoms - Array
// @param equals - optional comparator of atoms - returns true or false,
// if not specified, triple equals operator is used
// @returns Array - sequence of atoms, one of LCSs, edit script from A to B
var LCS = function (A, B, /* optional */ equals) {
// We just compare atoms with default equals operator by default
if (equals === undefined)
equals = function (a, b) { return a === b; };
// NOTE: all intervals from now on are both sides inclusive
// Get the points in Edit Graph, one of the LCS paths goes through.
// The points are located on the same diagonal and represent the middle
// snake ([D/2] out of D+1) in the optimal edit path in edit graph.
// @param startA, endA - substring of A we are working on
// @param startB, endB - substring of B we are working on
// @returns Array - [
// [x, y], - beginning of the middle snake
// [u, v], - end of the middle snake
// D, - optimal edit distance
// LCS ] - length of LCS
var findMidSnake = function (startA, endA, startB, endB) {
var N = endA - startA + 1;
var M = endB - startB + 1;
var Max = N + M;
var Delta = N - M;
var halfMaxCeil = (Max + 1) / 2 | 0;
var foundOverlap = false;
var overlap = null;
// Maps -Max .. 0 .. +Max, diagonal index to endpoints for furthest reaching
// D-path on current iteration.
var V = {};
// Same but for reversed paths.
var U = {};
// Special case for the base case, D = 0, k = 0, x = y = 0
V[1] = 0;
// Special case for the base case reversed, D = 0, k = 0, x = N, y = M
U[Delta - 1] = N;
// Iterate over each possible length of edit script
for (var D = 0; D <= halfMaxCeil; D++) {
// Iterate over each diagonal
for (var k = -D; k <= D && !overlap; k += 2) {
// Positions in sequences A and B of furthest going D-path on diagonal k.
var x, y;
// Choose from each diagonal we extend
if (k === -D || (k !== D && V[k - 1] < V[k + 1]))
// Extending path one point down, that's why x doesn't change, y
// increases implicitly
x = V[k + 1];
else
// Extending path one point to the right, x increases
x = V[k - 1] + 1;
// We can calculate the y out of x and diagonal index.
y = x - k;
if (isNaN(y) || x > N || y > M)
continue;
var xx = x;
// Try to extend the D-path with diagonal paths. Possible only if atoms
// A_x match B_y
while (x < N && y < M // if there are atoms to compare
&& equals(A[startA + x], B[startB + y])) {
x++; y++;
}
// We can safely update diagonal k, since on every iteration we consider
// only even or only odd diagonals and the result of one depends only on
// diagonals of different iteration.
V[k] = x;
// Check feasibility, Delta is checked for being odd.
if ((Delta & 1) === 1 && inRange(k, Delta - (D - 1), Delta + (D - 1)))
// Forward D-path can overlap with reversed D-1-path
if (V[k] >= U[k])
// Found an overlap, the middle snake, convert X-components to dots
overlap = [xx, x].map(toPoint, k); // XXX ES5
}
if (overlap)
var SES = D * 2 - 1;
// Iterate over each diagonal for reversed case
for (var k = -D; k <= D && !overlap; k += 2) {
// The real diagonal we are looking for is k + Delta
var K = k + Delta;
var x, y;
if (k === D || (k !== -D && U[K - 1] < U[K + 1]))
x = U[K - 1];
else
x = U[K + 1] - 1;
y = x - K;
if (isNaN(y) || x < 0 || y < 0)
continue;
var xx = x;
while (x > 0 && y > 0 && equals(A[startA + x - 1], B[startB + y - 1])) {
x--; y--;
}
U[K] = x;
if (Delta % 2 === 0 && inRange(K, -D, D))
if (U[K] <= V[K])
overlap = [x, xx].map(toPoint, K); // XXX ES5
}
if (overlap) {
SES = SES || D * 2;
// Remember we had offset of each sequence?
for (var i = 0; i < 2; i++) for (var j = 0; j < 2; j++)
overlap[i][j] += [startA, startB][j] - i;
return overlap.concat([ SES, (Max - SES) / 2 ]);
}
}
};
var lcsAtoms = [];
var lcs = function (startA, endA, startB, endB) {
var N = endA - startA + 1;
var M = endB - startB + 1;
if (N > 0 && M > 0) {
var middleSnake = findMidSnake(startA, endA, startB, endB);
// A[x;u] == B[y,v] and is part of LCS
var x = middleSnake[0][0], y = middleSnake[0][1];
var u = middleSnake[1][0], v = middleSnake[1][1];
var D = middleSnake[2];
if (D > 1) {
lcs(startA, x - 1, startB, y - 1);
if (x <= u) {
[].push.apply(lcsAtoms, A.slice(x, u + 1));
}
lcs(u + 1, endA, v + 1, endB);
} else if (M > N)
[].push.apply(lcsAtoms, A.slice(startA, endA + 1));
else
[].push.apply(lcsAtoms, B.slice(startB, endB + 1));
}
};
lcs(0, A.length - 1, 0, B.length - 1);
return lcsAtoms;
};
// Helpers
var inRange = function (x, l, r) {
return (l <= x && x <= r) || (r <= x && x <= l);
};
// Takes X-component as argument, diagonal as context,
// returns array-pair of form x, y
var toPoint = function (x) {
return [x, x - this]; // XXX context is not the best way to pass diagonal
};
// Wrappers
LCS.StringLCS = function (A, B) {
return LCS(A.split(''), B.split('')).join('');
};
// Exports
if (typeof module !== "undefined")
module.exports = LCS;