背景

有一个需求,希望一个输入框的字数统计功能进行优化,优化成与 Microsoft word 一致的统计字数的逻辑。

研究调查

Word 中的词数统计规则如下:

  1. 连续的非空 ascii 字符认为是一个词:如 hello world 为 2 个词
  2. 一个非 ascii 字符认为是一个词:如 你好啊! 为 4 个词
  3. 空格不认为是一个词,只是词的分隔符

你好! 为 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,其中图中右下角为操作动作的代码。

所以有了状态转换图后,实现算法起来就更加明确有底气了!,更加有据可依!

在这里我使用表驱动方式实现该状态机