npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

js-kmp-matcher

v1.0.2

Published

简单的 KMP 算法(即 Knuth–Morris–Pratt 算法)的实现,用于学习之目的,代码当中有每一部分的详细说明。

Downloads

8

Readme

JS KMP Matcher

简单的 KMP 算法(即 Knuth–Morris–Pratt 算法)的实现,用于学习之目的,代码当中有每一部分的详细说明。

算法简介

KMP 是一种字符串搜索算法。字符串搜索是指在 目标字符串 里搜索指定 关键字 所出现的位置,比如在各种文本编辑器里使用 "查找" 功能就是字符串搜索。

简单思想

  1. KMP 算法假设 关键字 字符串里的 子字符串(指从第一个字符开始到中间任意位置的一部分字符串)存在头尾相同的一个或多个字符的情况;
  2. 在暴力搜索(Brute-force search)的基础上,每当遇到字符不匹配的时候,则检查已匹配的部分有无存在 头尾相同的字符,有的话则跳过相同的字符。

示例:

start--v     v--i
.......aiiiiabcd.....  <-- 目标字符串
       aiiiiaz         <-- 关键字字符串
             ^--j

在上图中,当轮匹配从第一个字符 a 开始,然后比较每一个 ij 位置的字符,匹配则同时推进 ij 指针,当推进到字符 b 时,发现字符不匹配。对于暴力搜索,这时会把 i 指针回退到 start + 1j 回退到 0,然后开始新一轮匹配。

但 KMP 并不回退 i,它会通过一个事先创建好的 "关键字部分匹配表" 查找已匹配部分,即 aiiiia 有无头尾相同的字符,在当前这个例子里是存在的,相同的字符是 a,所以只需把关键字字符串右移 5 个字符位置,让 头 a 对齐 尾 a,(注,让关键字右移,实际上就是减少 j 的值,在这个例子里,j 要减去 5),然后从刚才停下来的位置开始继续匹配。

KMP 之所以能够如此 "大胆" 跳过 4 个字符,是因为它(通过查表)知道在头尾 a 字符之间不会再出现 a,所以比较这段字符串毫无意义。这时不妨想象一下对于暴力搜索,它会用关键字的第一个字符 a 去逐个比较目标字符串里的 iiiia,显然前面的 4 个 i 比较毫无必要。

下面是一个 头尾相同字符 由多个字符组成的例子:

start--v         v--i
.......abcabababcxyzdef.....  <-- 目标字符串
       abcabababci            <-- 关键字字符串
                 ^--j

在上图中,当匹配到字符 x 时,发现不匹配。而已匹配的部分,即 abcabababc 存在头尾相同的字符 abc,所以这时只需把关键字右移 7 个字符位置,让 头 abc 对齐 尾 abc,(即让 j 减去 7),然后从刚才停下来的位置开始继续匹配。

从上面的原理可知,KMP 加速的关键在于 关键字字符串 里存在重复的字符,对于小字符集(比如英文字母)或者更小的字符集会有比较良好的效果,但对于大字符集(比如中文)效果不明显,极端情况下如果 关键字字符串 里不存在任何重复的字符,实际上仍然是暴力搜索,而且还会多一个建表和查表过程,速度会比暴力搜索更慢。

建立 "关键字部分匹配表" 的方法

"关键字部分匹配表" 也就是关键字各子字符串的 "头尾相同的字符表",或者说 "最大公共前后缀表"。

假设关键子字符串是 "abcdabd",则 "关键字部分匹配表" 如下:

| 子字符串 | 头尾相同的字符 | 长度 | |---------|---------------|---------| | a | 单个字符不存在 | 0 | | ab | 无 | 0 | | abc | 无 | 0 | | abcd | 无 | 0 | | abcda | a | 1 | | abcdab | ab | 2 | | abcdabd | 无 | 0 |

用程序建立 "关键字部分匹配表" 的过程跟搜索过程一样,同样可以用暴力搜索法来创建,也可以用在建立过程中形成的 半成品表 来一次迷你的 KMP 搜索。

这个过程单纯用语言描述比较模糊,下面的是河马蜀黍自己乱想出来的实物演示法😅,动手做一下可以很快发现规律:

准备一条纸条,宽度为 1cm,长度为关键字字符的长度(即 length cm),然后每一格(cm)填上一个字母。这时这条纸条就是关键字字符数组。

关键字可以参考下面几个有代表性的:

  • 'abaab'
  • 'aaaab'
  • 'aabaaa'
  • 'abcdabd'

复制一条一模一样的纸条。

将两条纸条一上一下平铺,然后左右错开一格,再逐渐拉开,每次拉开一格,直到只剩下还连着一格为止,在拉开的过程中找到上下两条纸条内容重叠/相同的位置,这个重叠的长度就是 "最大公共前后缀长度"。

然后折掉最右边一格,重复上面的步骤,找到长度为 length - 1 时的 "最大公共前后缀长度"。

不断折掉最右边的一格,直到只剩下 2 格为止,找到所有长度下的 "最大公共前后缀长度"。(因为 1 个字符不存在前缀或后缀,所以不需要减到 1 个字符)。

实际计算时并不需要重复计算每一种长度的情况,只需计算一次最长的长度即可,因为由上面的演示可见,不同长度的前面的步骤都是 一模一样 的。

程序的过程如下:

如果 i (上纸条当前比较的字符的位置) 和 j (下纸条当前比较字符的位置) 的字符:
1. 不同:
   a. 如果 j == 0,则 i++
   b. 如果 j != 0, 有两种方式:
      I.  暴力搜索法:
          将 i 退回到第 i-j+1,即 i 和 j 第一次相同的后一个字符;
          将 j 退回到 0。
      II. 现做现用迷你 KMP 法,因为目前 table (尽管还不完整)已经记录了部分头尾相同的子字符串,
          j 不需要退回到 0,只需要将 j 退回到 table[j-1] 即可,即跳过头尾相同字符中间的那些必然不会匹配的字符。
          i 保持不变。
2. 相同:则 i++, j++

在计算过程中,j 的值就是关键字子字符串长度为 i 时的 "最大公共前后缀长度" 。

单元测试

执行命令:

$ npm test

应该能看到 testKMP() passed. 字样。

单步调试/跟踪

有时可能跟踪程序的运行也能帮助对程序的理解,启动单步调试的方法是:

在 vscode 里打开该项目,然后在单元测试文件 test-all.js 或者源码 kmp.js 里设置断点,然后执行 Run and Debug 即可。

字符串搜索算法系列项目

  • JS Rabin-Karp Matcher https://github.com/hemashushu/js-rabin-karp-matcher

  • JS Boyer-Moore-Horspool Matcher https://github.com/hemashushu/js-boyer-moore-horspool-matcher

  • JS KMP Matcher https://github.com/hemashushu/js-kmp-matcher

  • JS Aho-Corasick Matcher https://github.com/hemashushu/js-aho-corasick-matcher

  • JS Regexp Interpreter https://github.com/hemashushu/js-regexp-interpreter