整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

用C和JS模仿实现Sublime的模糊匹配功能

用C和JS模仿实现Sublime的模糊匹配功能

ublime Text是很多码农最喜欢的编程利器。虫虫也是是它的拥趸之一。它启动快趁手好用,通过设置你打开的文件,下次还会继续打开,在你电脑出现故障(比如蓝屏)之后,再次打开还能回到你工作状态,不怕丢失内容。还有很多很便捷实用的功能,比如utf8编码转换(去除bom)。当然还有一个我最喜欢的一个的功能,模糊搜索,这是本文今天的要说的主角。

用模糊匹配来过滤文件和函数的速度非常快。网上的有很多人都想知道如何工作的。但是还没有一个令人满意的答案。我决定对其探索研究一番。

Sublime的模糊匹配

Sublime有两个超级方便的导航功能。一个用于查找文件,另一个用于查找字符(函数,类名等)。两者工作方式都相同。我们不必完全正确地输入文件名,只需键入文件名中包含的字母即可。文件模糊查询模式的进入是通过快捷键Crtl+P(mac下是?+P)。进入模糊匹配模式然后输入几个字母,搜索会用字母做模糊匹配然后将匹配的文件列出了以供快速点击进入,比如我们输入js:

这是一个文件搜索。我进入搜索模式'js'。最上面的结果是'japanese-sjis.inc.php'。每个结果中的匹配字符都以粗体高亮显示,这个结果如果熟悉正则的朋友应该知道好像就是模式匹配/j.*s/

下面是另一个例子,我们在搜索框中先输入@会进入当前文件的函数搜索模式。

比如我们输入"add"会显示以包含add所有函数和方法。同时文件中的相关文件就会高亮显示,比如本例中的"AddAccessoryCommand"。点击列表中的函数就会直接定位到函数的定义。

一点想法

sublime模糊匹配非常好用,非常强大。但是其他的地方都没有这样的功能,包括其他的编辑器、IDE等。

好奇心促使我们相对其做一些探索,然后可能的话将其功能想办法嫁接到其他项目使用。

功能探索

如果我们拿Sublime Text做实验,我们会观察到两件事情:

模糊匹配会按顺序匹配每个字符。

有一个隐藏的匹配打分,其中有一些匹配的字符比其他字符的分值多。

我们可以轻松实现第一部分。我们自己实现下吧吧!

瞧!目前,我们可以很容易实现了C++和JavaScript的简单版本。我这样做是出于一个非常具体的原因。它可以用来取代简单的子串匹配。

匹配打分

有趣的部分打分。打分时候考虑到什么因素得?这新因素都会给打多少分?首先,下面是可能设计的打分点:

匹配的字母

未匹配的字母

连续匹配的字母

接近开始

分隔符后的字母(空格符包括空格,下划线)

写字母后面的大写字母(又名CamelCase,驼峰命名法)

这部分很简单。匹配的字母加分。不匹配字母减分。匹配接近开始加分。匹配短语中间的第一个字母加分。在驼峰命名案例中匹配大写字母加分。

当然具体怎么加分,加多少分?目前我还不知道正确的答案。权重取决于你的预期数据集。文件路径与文件名不同。文件扩展名则可以忽略的。单个单词关心连续的匹配,但不包括分隔符或骆驼大小写。

目前我们定义了一个各项指标的权衡。它对很多不同的数据集都有很好的效果。

分数从0开始

匹配的字母:+0分

不匹配的字母:-1点

连续匹配加分:+5分

分隔符匹配加分:+10分

驼峰匹配加分:+10分

不匹配的大写字母:-3分(最大-9)

需要指出的是打分值没有啥实在的意义,只作为一个相对比较的参考。得分范围也没有限定到0-100不。它大概是-50-50之间是]。由于不匹配的字母减分,较长的单词具有可能会得到比较低的最低分值。由于匹配加分,更长的搜索模式可能更可能得到最高分。

分隔符和驼峰加分比较大。连续的匹配加分比较有意义。

如果你不匹配前三个字母,会减分。如果在开始附近匹配会加分。中间和结束之间的匹配没有区别。

完全匹配没有明确的加分机制。不匹配的字母有会减分。所以更短的字符串和更近的匹配会得分更大。

大概的加分情况就是这样。对于单个搜索模式,结果可以按分数排序。

搜索性能

Grep很快。真的很快。它高度优化,不需要测试每个字母。它可以跳过开头。

模糊匹配速度不如grep快。它需要测试搜索字符串中的每个字母。虽然我写了我认为干净的代码,但它并没有经过大量的优化。作为演示目的,在可读性上做了一定的考虑。

我电脑的CPU是Intel i5-4670 Haswell @ 3.4Ghz。将模式与Unreal Engine 4中找到的13,164个文件名匹配,在单个线程上花费约5毫秒。使用355,000个字对英文单词列表进行测试需要大约50毫秒。 JavaScript没有C++快。实际的测试红总,它大概慢25倍。可能有一些明显的改进空间。提供了异步帮助程序,因此脚本不会在慢速搜索中阻止。

总结

我喜欢Sublime Text以及他的模糊匹配算法。我的目标模仿他实现相同同能的代码。我认为我实现了这个目标,最后附上两种语言的实现源码,有兴趣的同学可以参考学习。

源代码一 C实现:

#ifndef FTS_FUZZY_MATCH_H

#define FTS_FUZZY_MATCH_H

#include <cstdint>

#include <ctype.h>

#include <cstring>

#include <cstdio>

namespace fts {

static bool fuzzy_match_simple(char const * pattern, char const * str);

static bool fuzzy_match(char const * pattern, char const * str, int & outScore);

static bool fuzzy_match(char const * pattern, char const * str, int & outScore, uint8_t * matches, int maxMatches);

}

#ifdef FTS_FUZZY_MATCH_IMPLEMENTATION

namespace fts {

namespace fuzzy_internal {

static bool fuzzy_match_recursive(const char * pattern, const char * str, int & outScore, const char * strBegin,

uint8_t const * srcMatches, uint8_t * newMatches, int maxMatches, int nextMatch,

int & recursionCount, int recursionLimit);

}

static bool fuzzy_match_simple(char const * pattern, char const * str) {

while (*pattern !='>while (*pattern !='>while (*pattern !='\0' && *str !='\0') {<' && *str !='>while (*pattern !='\0' && *str !='\0') {<') {<' && *str !='>while (*pattern !='\0' && *str !='\0') {<') {

if (tolower(*pattern)==tolower(*str))

++pattern;

++str;

}

return *pattern=='>return *pattern=='\0' ? true : false;<' ? true : false;

}

static bool fuzzy_match(char const * pattern, char const * str, int & outScore) {

uint8_t matches[256];

return fuzzy_match(pattern, str, outScore, matches, sizeof(matches));

}

static bool fuzzy_match(char const * pattern, char const * str, int & outScore, uint8_t * matches, int maxMatches) {

int recursionCount=0;

int recursionLimit=10;

return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, nullptr, matches, maxMatches, 0, recursionCount, recursionLimit);

}

static bool fuzzy_internal::fuzzy_match_recursive(const char * pattern, const char * str, int & outScore,

const char * strBegin, uint8_t const * srcMatches, uint8_t * matches, int maxMatches,

int nextMatch, int & recursionCount, int recursionLimit)

{

++recursionCount;

if (recursionCount >=recursionLimit)

return false;

if (*pattern=='>if (*pattern=='\0' || *str=='\0')<' || *str=='>if (*pattern=='\0' || *str=='\0')<')

return false;

bool recursiveMatch=false;

uint8_t bestRecursiveMatches[256];

int bestRecursiveScore=0;

bool first_match=true;

while (*pattern !='\0' && *str !='\0') {

if (tolower(*pattern)==tolower(*str)) {

if (nextMatch >=maxMatches)

return false;

if (first_match && srcMatches) {

memcpy(matches, srcMatches, nextMatch);

first_match=false;

}

uint8_t recursiveMatches[256];

int recursiveScore;

if (fuzzy_match_recursive(pattern, str + 1, recursiveScore, strBegin, matches, recursiveMatches, sizeof(recursiveMatches), nextMatch, recursionCount, recursionLimit)) {

if (!recursiveMatch || recursiveScore > bestRecursiveScore) {

memcpy(bestRecursiveMatches, recursiveMatches, 256);

bestRecursiveScore=recursiveScore;

}

recursiveMatch=true;

}

matches[nextMatch++]=(uint8_t)(str - strBegin);

++pattern;

}

++str;

}

bool matched=*pattern=='>bool matched=*pattern=='\0' ? true : false;<' ? true : false;

if (matched) {

const int sequential_bonus=15;

const int separator_bonus=30;

const int camel_bonus=30;

const int first_letter_bonus=15;

const int leading_letter_penalty=-5;

const int max_leading_letter_penalty=-15;

const int unmatched_letter_penalty=-1;

while (*str !='>while (*str !='\0')<')

++str;

outScore=100;

int penalty=leading_letter_penalty * matches[0];

if (penalty < max_leading_letter_penalty)

penalty=max_leading_letter_penalty;

outScore +=penalty;

int unmatched=(int)(str - strBegin) - nextMatch;

outScore +=unmatched_letter_penalty * unmatched;

for (int i=0; i < nextMatch; ++i) {

uint8_t currIdx=matches[i];

if (i > 0) {

uint8_t prevIdx=matches[i - 1];

if (currIdx==(prevIdx + 1))

outScore +=sequential_bonus;

}

if (currIdx > 0) {

char neighbor=strBegin[currIdx - 1];

char curr=strBegin[currIdx];

if (::islower(neighbor) && ::isupper(curr))

outScore +=camel_bonus;

bool neighborSeparator=neighbor=='_' || neighbor==' ';

if (neighborSeparator)

outScore +=separator_bonus;

}

else {

outScore +=first_letter_bonus;

}

}

}

if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {

memcpy(matches, bestRecursiveMatches, maxMatches);

outScore=bestRecursiveScore;

return true;

}

else if (matched) {

return true;

}

else {

return false;

}

}

}

#endif // FTS_FUZZY_MATCH_IMPLEMENTATION

#endif // FTS_FUZZY_MATCH_H

源代码二 JS代码实现:

function fuzzy_match_simple(pattern, str) {

var patternIdx=0;

var strIdx=0;

var patternLength=pattern.length;

var strLength=str.length;

while (patternIdx !=patternLength && strIdx !=strLength) {

var patternChar=pattern.charAt(patternIdx).toLowerCase();

var strChar=str.charAt(strIdx).toLowerCase();

if (patternChar==strChar)

++patternIdx;

++strIdx;

}

return patternLength !=0 && strLength !=0 && patternIdx==patternLength ? true : false;

}

function fuzzy_match(pattern, str) {

var adjacency_bonus=5;

var separator_bonus=10;

var camel_bonus=10;

var leading_letter_penalty=-3;

var max_leading_letter_penalty=-9;

var unmatched_letter_penalty=-1;

var score=0;

var patternIdx=0;

var patternLength=pattern.length;

var strIdx=0;

var strLength=str.length;

var prevMatched=false;

var prevLower=false;

var prevSeparator=true;

var bestLetter=null;

var bestLower=null;

var bestLetterIdx=null;

var bestLetterScore=0;

var matchedIndices=[];

while (strIdx !=strLength) {

var patternChar=patternIdx !=patternLength ? pattern.charAt(patternIdx) : null;

var strChar=str.charAt(strIdx);

var patternLower=patternChar !=null ? patternChar.toLowerCase() : null;

var strLower=strChar.toLowerCase();

var strUpper=strChar.toUpperCase();

var nextMatch=patternChar && patternLower==strLower;

var rematch=bestLetter && bestLower==strLower;

var advanced=nextMatch && bestLetter;

var patternRepeat=bestLetter && patternChar && bestLower==patternLower;

if (advanced || patternRepeat) {

score +=bestLetterScore;

matchedIndices.push(bestLetterIdx);

bestLetter=null;

bestLower=null;

bestLetterIdx=null;

bestLetterScore=0;

}

if (nextMatch || rematch) {

var newScore=0;

if (patternIdx==0) {

var penalty=Math.max(strIdx * leading_letter_penalty, max_leading_letter_penalty);

score +=penalty;

}

if (prevMatched)

newScore +=adjacency_bonus;

if (prevSeparator)

newScore +=separator_bonus;

if (prevLower && strChar==strUpper && strLower !=strUpper)

newScore +=camel_bonus;

if (nextMatch)

++patternIdx;

if (newScore >=bestLetterScore) {

if (bestLetter !=null)

score +=unmatched_letter_penalty;

bestLetter=strChar;

bestLower=bestLetter.toLowerCase();

bestLetterIdx=strIdx;

bestLetterScore=newScore;

}

prevMatched=true;

}

else {

formattedStr +=strChar;

score +=unmatched_letter_penalty;

prevMatched=false;

}

prevLower=strChar==strLower && strLower !=strUpper;

prevSeparator=strChar=='_' || strChar==' ';

++strIdx;

}

if (bestLetter) {

score +=bestLetterScore;

matchedIndices.push(bestLetterIdx);

}

var formattedStr="";

var lastIdx=0;

for (var i=0; i < matchedIndices.length; ++i) {

var idx=matchedIndices[i];

formattedStr +=str.substr(lastIdx, idx - lastIdx) + "<b>" + str.charAt(idx) + "</b>";

lastIdx=idx + 1;

}

formattedStr +=str.substr(lastIdx, str.length - lastIdx);

var matched=patternIdx==patternLength;

return [matched, score, formattedStr];

}

function fts_fuzzy_match_async(matchFn, pattern, dataSet, onComplete) {

var ITEMS_PER_CHECK=1000;

var max_ms_per_frame=1000.0/30.0; /

var dataIndex=0;

var results=[];

var resumeTimeout=null;

function step() {

clearTimeout(resumeTimeout);

resumeTimeout=null;

var stopTime=performance.now() + max_ms_per_frame;

for (; dataIndex < dataSet.length; ++dataIndex) {

if ((dataIndex % ITEMS_PER_CHECK)==0) {

if (performance.now() > stopTime) {

resumeTimeout=setTimeout(step, 1);

return;

}

}

var str=dataSet[dataIndex];

var result=matchFn(pattern, str);

if (matchFn==fuzzy_match_simple && result==true)

results.push(str);

else if (matchFn==fuzzy_match && result[0]==true)

results.push(result);

}

onComplete(results);

return null;

};

TML5的结构化标签,对搜索引擎更友好

li 标签对不利于搜索引擎的收录,尽量少用

banner图片一般拥有版权,不需要搜索引擎收录,因此可以使用ul + li

<samp></samp>可用于浅色副标题

display:inline-block; 每个导航块存在水平间隙,解决方法是在父元素上添加font-size:0;


sublime安装csscomb插件

选中css代码,ctrl+shift+c 自动整理好代码

排序前:

排序后:

此时存在多余的空行

解决方法:

安装cssformat插件,对代码执行edit->cssformat->expanded 即可删除空行


选中单句样式前面的空白部分(即tab空位)

然后alt+f3 会统一选中所有tab留白

按一次删除,再按一次删除,再空一格

此时这个效果:

然后按向下箭头,按向左箭头,按删除一次,再加个空格

此时效果

再向下箭头,再删除

此时效果

css样式代码美化完毕。

新标签元素的浏览器兼容解决:

header,nav,section,aside,article,footer{display: block;}

最后晒出所有代码

index.html

<!DOCTYPE html>
<html lang="en" manifest="index.manifest">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
    <header>
        <div class="container">
            <a href="#"><img src="cat-little.jpg"></a>
            <nav>
                <a href="#" class="active">导航</a>
                <a href="#">导航</a>
                <a href="#">导航</a>
                <a href="#">导航</a>
                <a href="#">导航</a>
            </nav>
        </div>
    </header>

    <section class="banner">
        <ul>
            <li class="left"><img src="banner1.jpg"></li>
            <li class="active"><img src="banner3.jpg"></li>
            <li class="right"><img src="banner2.jpg"></li>
        </ul>
    </section>

    <section class="main">
        <aside>
            <h1>左边<samp>标题</samp></h1>
            <dl>
                <dt>小标题</dt>
                <dd class="text">文字内容哦~</dd>
                <dd class="pic"><img src="p1.jpg"></dd>
            </dl>
            <dl>
                <dt>小标题</dt>
                <dd class="text">文字内容哦~</dd>
                <dd class="pic"><img src="p2.jpg"></dd>
            </dl>
            <dl>
                <dt>小标题</dt>
                <dd class="text">文字内容哦~</dd>
                <dd class="pic"><img src="p3.jpg"></dd>
            </dl>
        </aside>
        <article>
            <h1>右边<samp>标题</samp></h1>
            <p>这是右边文章内容哦~</p>
            <img src="qrt.jpg">
            <p>这是右边文章内容哦~</p>
        </article>
    </section>

    <footer>
        <div class="container">
            <p>版权信息</p>
            <span>
                <img src="b1.jpg">
                <img src="b2.jpg">
                <img src="b3.jpg">
            </span>
        </div>        
    </footer>
</body>
</html>

style.css

* { font-size: 14px; margin: 0; padding: 0; border: none;}
a { text-decoration: none;}
ul { list-style: none;}

/*浏览器兼容解决*/
header,nav,section,aside,article,footer{display: block;}

/*头部*/
header { width: 100%; height: 60px; background-color: #000;}
.container { width: 1200px; margin: 0 auto;}
.container > a { display: block; float: left; width: 150px; height: 60px; padding-left: 10px;}
.container > a img { height: 40px; padding-top: 10px;}
nav { font-size: 0; float: right; padding-right: 10px;}
nav > a { font-size: 16px; line-height: 60px; display: inline-block; width: 100px; height: 60px; cursor: pointer; text-align: center; color: #fff;}
nav > a:first-child { background: rgb(212, 1, 18);}
nav > a:nth-child(2) { background: rgb(254, 184, 0);}
nav > a:nth-child(3) { background: rgb(120, 185, 23);}
nav > a:nth-child(4) { background: rgb(242, 124, 1);}
nav > a:last-child { background: rgb(1, 127, 203);}
nav > a:hover, nav > a.active { padding-bottom: 5px;}

/*banner*/
.banner { background: #ccc;}
.banner ul { position: relative; width: 1200px; height: 400px; margin: 0 auto; padding-top: 100px;}
.banner ul li { position: absolute; z-index: 1; top: 0; bottom: 0; overflow: hidden; width: 500px; height: 250px; margin: auto;}
.banner ul li img { overflow: hidden; width: 100%;}
.banner ul li.active { z-index: 2; right: 0;    /*设置水平居中*/ left: 0; width: 600px; height: 300px;}
.banner ul li.left { /*设置在左边*/ left: 0;}
.banner ul li.right { /*设置在右边*/ right: 0;}

/*主体部分*/
.main { width: 1200px; height: 350px; margin: 0 auto;}
aside { float: left; width: 600px;}
article { float: right; width: 600px;}
.main h1 { font-size: 24px; font-weight: lighter; margin: 20px 0;}
.main h1 samp { color: #7d7d7d;}
aside img { height: 70px;}
aside dl { position: relative; margin-bottom: 10px;}
aside dl dt { position: absolute; top: 0; left: 90px;}
aside dd.pic { overflow: hidden; width: 70px; height: 70px;}
aside dd.text { position: absolute; top: 20px; left: 90px;}
article > img { overflow: hidden; height: 130px;}
article > p { margin-bottom: 10px;}

/*底部*/
footer { background-color: #000;}
footer > .container { width: 1200px; height: 60px; margin: 0 auto;}
footer > .container > p { line-height: 60px; float: left; color: #fff;}
footer > .container > span { float: right;}
footer > .container > span > img { width: 25px; height: 25px; margin-left: 10px; padding-top: 17px; cursor: pointer; opacity: .7;}
footer > .container > span > img:hover { opacity: 1;}
/*# sourceMappingURL=style.css.map */

效果图

原文链接:https://www.cnblogs.com/chenyingying0/p/12250255.html


喜欢小编的可以点个赞关注小编哦,小编每天都会给大家分享文章。

我自己是一名从事了多年的前端老程序员,小编为大家准备了新出的前端编程学习资料,免费分享给大家!

如果你也想学习前端,可以观看【置顶】文章。也可以私信【1】 领取最新前端练手实战项目

ublime Text 是一款功能强大、界面简洁、高度可定制的代码编辑器。它提供了丰富的功能和快捷操作,让编码变得高效而愉悦。在 Mac 系统上,Sublime Text 表现出色,成为众多开发者和程序员的首选编辑器之一。无论是日常的代码编写还是项目管理,Sublime Text 都能够满足用户的需求,并带来高效的开发体验。

Sublime Text 内置了许多快捷操作,使得代码编辑更加高效。例如,使用鼠标右键可以直接打开命令面板,通过输入关键字快速执行各种操作;使用快捷键组合可以快速切换文件、移动和复制行、跳转到指定行等。这些快捷操作大大减少了操作的步骤和时间,提高了编码效率。

将链接地址复制到浏览器地址栏回车即可打开下载

Mac安装:https://mac.macsc.com/mac/3147.html?id=NDAyNTUy

Win安装:https://soft.macxf.com/soft/809.html?id=MTcyMDc1