-
Notifications
You must be signed in to change notification settings - Fork 0
/
longestCommonSubstring.js
142 lines (114 loc) · 4.16 KB
/
longestCommonSubstring.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
display("abcdefghi", "abcdxfghi");
display("I like cats", "I like dogs");
display("I like cats", "I like bats");
display("I like cats", "I like dinosaurs");
display("<a href='#' onclick='alert(1)'>click me!</a>", "nice try");
display("スポーツは大好き", "スポーツが大好き");
function display(a, b) {
const container = document.querySelector("#container");
const common = findLongestCommonSubstring(a, b);
console.log(a, b, common);
const processedA = process(a, common);
const domNodesA = makeDomNodes(processedA, "removed");
const pA = document.createElement("p");
domNodesA.forEach(node => pA.appendChild(node));
container.appendChild(pA);
const processedB = process(b, common);
const domNodesB = makeDomNodes(processedB, "added");
const pB = document.createElement("p");
domNodesB.forEach(node => pB.appendChild(node));
container.appendChild(pB);
container.appendChild(document.createTextNode("--------"))
}
function findLongestCommonSubstring(str1, str2) {
const memo = initMemo(str1, str2);
const longestCommonArray = helper(str1.split(""), str2.split(""), 0, 0, memo);
return longestCommonArray.join("");
}
function helper(arr1, arr2, i1, i2, memo) {
if (arr1.length === i1 || arr2.length === i2) {
return [];
}
if (memo[i1][i2] !== undefined) {
return memo[i1][i2];
}
if (arr1[i1] === arr2[i2]) {
const result =[arr1[i1], ...helper(arr1, arr2, i1 + 1, i2 + 1, memo)];
memo[i1][i2] = result;
return result;
}
const result1 = helper(arr1, arr2, i1 + 1, i2, memo);
const result2 = helper(arr1, arr2, i1, i2 + 1, memo);
const result = result1.length > result2.length ? result1 : result2;
memo[i1][i2] = result;
return result;
}
// I'm too lazy to search for the ninja one-liner that doubtlessly exists for this:
function initMemo(str1, str2) {
const out = [];
for (let i = 0; i < str1.length; i++) {
// this array is going to be filled in out of order
out[i] = [];
}
return out;
}
// This `process` function has a terrible name, but its goal is to take the string and the
// longest common substring, and break the string apart into parts that are in the common
// substring and parts that aren't.
//
// If the original string is "I like cats" and the common substring is "I like s" (i.e.
// "cats" has been changed to another animal) the output will look like this:
// [ { content: "I like ", type: "same"}, {content: "cat", type: "different"}, {content: "s", type: "same"} ]
function process(str, common) {
const output = [];
let buffer = [];
let state = "same";
let s = 0, c = 0;
// common is expected to be shorter or equal to str
while (s < str.length) { // check this
if (state === "same") {
if (str[s] === common[c]) {
buffer.push(str[s]);
s++;
c++;
} else {
output.push({ content: buffer.join(""), type: "same" });
state = "different";
buffer = [];
buffer.push(str[s]);
s++;
}
} else if (state === "different") {
if (str[s] !== common[c]) {
buffer.push(str[s]);
s++;
} else {
output.push({ content: buffer.join(""), type: "different" })
state = "same";
buffer = [];
buffer.push(str[s]);
s++;
c++;
}
} else {
throw new Error("unexpected state", state);
}
}
if (buffer.length > 0) {
output.push({content: buffer.join(""), type: state});
}
return output;
}
function makeDomNodes(processed, accentClass) {
return processed
.map(part => {
if (part.type === "same") {
return document.createTextNode(part.content);
} else {
const newNode = document.createElement("span");
newNode.classList.add(accentClass);
newNode.textContent = part.content;
return newNode;
}
});
}