写在前面
- 准备整一下 “基于KMP的FSM处理 pattern” 啦,祝我成功 🙋
- 这里呢~ 准备分为三步去实现
- FSM 处理字符串
- KMP 算法概述
- 基于 KMP 的 FSM 处理 pattern 的实现
实践记录
FSM 处理字符串
FSM 概念
- 每一个状态都是一个机器
- 在每一个机器里,我们可以做计算、存储、输出
- 所有的这些机器接受的输入是一致的
- 状态机的每一个机器本身没有状态,如果我们用函数来表示的话,它应该是纯函数(无副作用)
- 每一个机器知道下一个状态
- 每个机器都有确定的下一个状态(Moore)
- 每个机器根据输入决定下一个状态(Mealy)
在一个字符串中,找到字符串 abcdef
不使用 FSM ,在一个字符串中,找到字符串 abcdef找到字符串 abcdef
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
31function match(string) {
let foundA = false
let foundB = false
let foundC = false
let foundD = false
let foundE = false
for (let char of string) {
if (char == 'a')
foundA = true
else if (foundA && char == 'b')
foundB = true
else if (foundB && char == 'c')
foundC = true
else if (foundC && char == 'd')
foundD = true
else if (foundD && char == 'e')
foundE = true
else if (foundD && char == 'f')
return true
else {
let foundA = false
let foundB = false
let foundC = false
let foundD = false
let foundE = false
}
}
return false
}
console.log(match("I aabcdefghm grood."))
console.log(match("I aacdefghm grood."))- 运行结果
- 缺点:
- 不可扩展代码较多
- if else 多次判断
- 运行结果
使用 FSM ,在一个字符串中,找到字符串 abcdef
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
48function match(string) {
let state = start
for (let char of string) {
state = state(char)
}
return state === end
}
function start(char) {
if (char === 'a')
return findA
else
return start
}
function end(char) {
return end
}
function findA(char) {
if (char === 'b')
return findB
else
return start(char)
}
function findB(char) {
if (char === 'c')
return findC
else
return start(char)
}
function findC(char) {
if (char === 'd')
return findD
else
return start(char)
}
function findD(char) {
if (char === 'e')
return findE
else
return start(char)
}
function findE(char) {
if (char === 'f')
return end
else
return start
}
console.log(match("I aabcdefghm grood."))
console.log(match("I aacdefghm grood."))- 运行结果
- 运行结果
JS 中的有限状态机
1 | // 每个函数是一个状态 |
使用 FSM ,在一个字符串中,找到字符串 abcabx
1 |
|
- 运行结果
使用 FSM ,在一个字符串中,找到字符串 ababx
1 | function match(string) { |
- 运行结果
【那我们该如何利用 FSM 处理未知 pattern 呢?🤔
【字符串匹配问题就是在一个文本字符串T中搜索某个模式字符串P的所有出现位置。
KMP 算法概述
KMP 最长公共前后缀长度
假如 pattern 为 ABABCABAA
- 前缀依次为 A AB ABA ABAB ABABC ABABCA ABABCAB ABABCABA
- 后缀依次为 A AA BAA ABAA CABAA BCABAA ABCABAA BABCABAA
那么它的最长公共前后缀长度为多少呢?
- A –> 0
- 无前缀
- 无后缀
- 最长公共前后缀:””,len = 0
- AB –> 0
- 前缀 A
- 后缀 B
- 最长公共前后缀:””,len = 0
- ABA –> 1
- 前缀 A AB
- 后缀 A BA
- 最长公共前后缀:A,len = 1
- ABAB –> 2
- 前缀 A AB ABA
- 后缀 B AB BAB
- 最长公共前后缀:AB,len = 2
- ABABC –> 0
- 前缀 A AB ABA ABAB
- 后缀 C BC ABC BABC
- 最长公共前后缀:””,len = 0
- ABABCA –> 1
- 前缀 A AB ABA ABAB ABABC
- 后缀 A CA BCA ABCA BABCA
- 最长公共前后缀:A,len = 1
- ABABCAB –> 2
- 前缀 A AB ABA ABAB ABABC ABABCA
- 后缀 B AB CAB BCAB ABCAB BACAB
- 最长公共前后缀:AB,len = 2
- ABABCABA –> 3
- 前缀 A AB ABA ABAB ABABC ABABCA ABABCAB
- 后缀 A BA ABA CABA BCABA ABCABA BABCABA
- 最长公共前后缀:ABA,len = 3
- ABABCABAA –> 1
- 前缀 A AB ABA ABAB ABABC ABABCA ABABCAB ABABCABA
- 后缀 A AA BAA ABAA CABAA BCABAA ABCABAA BABCABAA
- 最长公共前后缀:A,len = 1
- A –> 0
因此我们可以构建出,最长公共前后缀长度为
- [0, 0, 1, 2, 0, 1, 2, 3, 1] 对应 pattern 字符串数组
- [A, B, A, B, C, A, B, A, A]
- [0, 1, 2, 3, 4, 5, 6, 7, 8]
那么根据
A –> len = 0, i = 0
AB –> len = 0, i = 1
ABA –> len = 1, i = 2, pattern[i] = A, pattern[上一个len] = A
ABAB –> len = 2, i = 3, pattern[i] = B, pattern[上一个len] = B
ABABC –> len = 0, i = 4
ABABCA –> len = 1, i = 5, pattern[i] = A, pattern[上一个len] = A
ABABCAB –> len = 2, i = 6, pattern[i] = B, pattern[上一个len] = B
ABABCABA –> len = 3, i = 7, pattern[i] = A, pattern[上一个len] = A
ABABCABAA –> len = 1, i = 8
我们可以观察得到
1
2
3
4
5if (pattern[i] === pattern[len]) {
len ++;
prefix[i] = len;
i ++;
}除此之外的情况呢?比如最后的 ABABCABAA –> len = 1, i = 8
- [0, 1, 2, 3, 4, 5, 6, 7, 8] 字符串数组下标索引
- [A, B, A, B, C, A, B, A, A] 字符串数组
- [0, 0, 1, 2, 0, 1, 2, 3, ?] prefixTable
- 我们可以发现,前一个字符 A 对应的 prefix 为 3,对应的pattern[3]为 B(prefix[len - 1] === B),B的前一个字符 A 对应的 prefix 为 1(len = prefix[len - 1] –> 0),pattern[1] 为 A,再执行上层代码 prefix[8] === len[0]
KMP 最长公共前后缀长度实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21function prefixTable(pattern, prefix, n) {
prefix[0] = 0; // prefix 初始
let len = 0; // 最长公共前后缀长度
let i = 1;
while(i < n) {
if (pattern[i] === pattern[len]) {
len ++;
prefix[i] = len;
i ++;
} else {
if (len > 0) {
len = prefix[len - 1]
}
else {
prefix[i] = len
i ++
}
}
console.log(prefix)
}
}运行结果
再添一层处理,因为前缀是不不会包括自己本身的,所以我们需要整体后移一位,并且,空出来的第一项置为 -1
1
2
3
4
5
6function movePrefixTable(prefix, n) {
for (let i = n - 1; i > 0; i --) {
prefix[i] = prefix[i - 1]
}
prefix[0] = -1
}
KMP 处理 pattern
1 | function prefixTable(pattern, prefix, n) { |
- 运行结果
基于 KMP 的 FSM 处理 pattern
1 |
|
- 运行结果
参考
写在后面
- 感谢在 KMP 算法折腾时,提供帮助的 田神大大【 🤫 等我有他联系方式了,我悄悄同步给你们
- 学而不思则罔
- 祝大家多多发财