写在前面
你一定在编辑器中用过搜索文件功能【command + P】,不需要输入完整的地址,也能匹配到你想要的文件。有点像模糊搜索,但又更智能一点,因为它不需要你输入的字符在目标字符里是连着的,就像下面这样【可以看到很多结果里面的字符是分散的】:
如何匹配
想了一圈字符串相关的匹配,又想到最后出现这种散列的结果,应该就是最长公共子序列了。
最长公共子序列
子序列的定义是:
如"a","abd", "ace"都是"abcde"的子序列。
所以最长公共子序列也很好理解了,就像下面这样:
let a = "callmedk";
let b = "faldkc";
let res = "aldk"; // res是a和b的最长公共子序列
实现
核心内容是动态规划,即
设n1,n2分别为str1和str2的下标,状态方程S(n1, n2)有:
当str1[n1] != str2[n2]时,S(n1, n2) = max(S(n1 - 1, n2), S(n1, n2 - 1));
当str1[n1] == str2[n2]时,S(n1, n2) = S(n1 - 1, n2 - 1) + 1;
function longestChild(str1, str2) {
// 记录数组
let record = Array.from({ length: str1.length }, () => {
return Array.from({ length: str2.length });
});
function find(index1, index2) {
if (index1 < 0 || index2 < 0) return { len: 0, match: [] };
if (record[index1][index2] !== undefined) return record[index1][index2];
let ans = record[index1][index2] = {
len: 0, // 长度
match: [], // 匹配的位置(两者的下标)
}
if (str1[index1] === str2[index2]) {
ans.len = 1;
ans.match.push([index1, index2]);
let chAns = find(index1 - 1, index2 - 1);
ans.len += chAns.len;
ans.match.push(...chAns.match);
} else {
let chAns1 = find(index1 - 1, index2);
let chAns2 = find(index1, index2 - 1);
ans = record[index1][index2] = chAns1.len > chAns2.len ? chAns1 : chAns2;
}
return ans;
}
// 稍微处理一下最终结果
let ans = find(str1.length - 1, str2.length - 1);
ans.match.reverse();
let len = ans.len;
let str = "";
for (let i = 0, l = ans.match.length; i < l; i++) {
str += str1[ans.match[i][0]]
};
return {
length: len,
str: str,
detail: ans
}
}
let ans = longestChild("callmedk", "faldkc");
console.log(ans);
结果:
完整流程
了解完最长公共子序列之后,来过一下整个路径匹配的流程
- 获取所有文件的路径
- 将搜索的字符串和所有路径做最长公共子序列的匹配【设字符串为s1】
- 筛选出长度与s1相同的结果,即为最终答案
编辑器好像还根据最近使用文件做了排序,这个方面没有研究细节
写个demo
function search(target, source) {
let res = [];
for (let i = 0, l = source.length; i < l; i++) {
let item = source[i];
let ans = longestChild(item, target);
if (ans.length === target.length) {
// 匹配的单个文字换成特殊文字
let lastIndex = -1;
let hItem = "";
for (let j = 0, k = ans.detail.match.length; j < k; j++) {
let charIndex = ans.detail.match[j][0];
let specialC = `<span style="color: red;">${item[charIndex]}</span>`;
hItem += (item.slice(lastIndex + 1, charIndex) + specialC);
lastIndex = charIndex;
}
if (lastIndex !== item.length - 1) {
hItem += item.slice(lastIndex + 1, item.length);
}
res.push(hItem);
}
}
return res;
}
let source = [
"abcdef",
"acbdefghij",
"abcdefghijklm",
"abcdefghijklmnopq",
"abcdefghijklmnopqrst",
"abcdefghijklmnopqrstuvw",
"abcdefghijklmnopqrstuvwxyz"
]
let ipt = document.createElement("input");
let container = document.createElement("div");
document.body.innerHTML = ``;
document.body.appendChild(ipt);
document.body.appendChild(container)
ipt.addEventListener("input", (e) => {
let content = e.target.value;
if (!content) {
container.innerHTML = ``;
return;
};
let res = search(content, source);
container.innerHTML = `
<div>
<h3>Search value: ${content}</h3>
<h3>Source: </h3>
<div>
${source.map(item => {
return `<div>${item}</div>`
}).join("")}
</div>
<h3>Search result: </h3>
<div>
${res.map(item => {
return `<div>${item}</div>`
}).join("")}
</div>
</div`
});