统计有效词数算法的几点思考
背景
有一个需求,希望一个输入框的字数统计功能进行优化,优化成与 Microsoft word 一致的统计字数的逻辑。
研究调查
Word 中的词数统计规则如下:
- 连续的非空 ascii 字符认为是一个词:如
hello world
为 2 个词 - 一个非 ascii 字符认为是一个词:如
你好啊!
为 4 个词 - 空格不认为是一个词,只是词的分隔符
如 你好!
为 3 个词, hello world~
为 2 个词
算法实现
虽然算法最终结果是希望我们得到最后的词数,但是我们应该输出的是把一个字符串分割后的数组,如 你好!
应该分割为 [' 你', '好', '! ']
,这样不仅通过该数组得到词数,还可以通过该数组做字符串 slice 操作,同时没有空格字符的丢失。
流程形算法
我们很容易想到的一种思路是,遍历字符串,进行直线式的逻辑处理
function isWordString(str) {
return /^[\x00-\xff]+$/.test(str) && !isSpaceString(str);
}
function isSpaceString(str) {
return /^\s+$/.test(str);
}
function wordChunks(string) {
let word = string.replace(/^(\s*)([^]*)$/, '$1');
// Trim left
string = string.replace(/^(\s*)([^]*)$/, '$2');
let chunks = [];
for (let i = 0; i < string.length; i++) {
const ch = string[i];
const nextCh = string[i + 1];
if (isSpaceString(ch)) {
word += ch;
} else if (isWordString(ch)) {
if (word) {
if (isSpaceString(word[word.length - 1]) && !isSpaceString(word)) {
chunks.push(word);
word = '';
}
}
word += ch;
} else {
if (nextCh && isSpaceString(nextCh)) {
word += ch;
} else {
if (word && isSpaceString(word)) {
chunks.push(word + ch);
} else {
word && chunks.push(word);
chunks.push(ch);
}
word = '';
}
}
}
word && chunks.push(word);
return chunks;
}
可以看到以上代码有很多的判断分支,维护起来也比较麻烦,像是想到哪里就写到哪里,漏了哪里补哪里的思路,维护成本高。
其实对于这种问题,我们可以把其使用有限状态机进行抽象
状态机思路
如下图所示,分别有5个状态:none/space/ascii-word/ready/end
, 其中
none
: 初始状态,表示 word 为空space
: 表示 word 只包含空格ascii-word
: 表示 word 包含非空 ascii 字符,不包含汉字等其他字符,但可以包含空格ready
: 表示 word 是一个独立的词,如"你"
或者"hello "
end
: 表示结束状态
其中通过不同的字符可以到达不同的状态,其中随着状态的变换,带着操作的执行,如 append/push/reset
,其中图中右下角为操作动作的代码。
所以有了状态转换图后,实现算法起来就更加明确有底气了!,更加有据可依!