网站搜索

javascript - 捕获一个字符串然后匹配所有以该字符串开头的其他单词


我有一个包含80,000多个单词的列表,每个单词由换行符分隔。我需要匹配每一个以较小单词为前缀的单词。例如,

bald    <-- captures bald
balder  <-- matches because it starts with bald
balding <-- matches because it starts with bald
care    <-- captures care
cared   <-- matches because it starts with care
cares   <-- matches because it starts with care
caring  <-- does NOT match because it does not start with care

我将会在 Sublime Text 中使用查找和替换,所以我希望能够使用“全部替换”为“”,从而将它们从我的列表中移除。

好的,故事背景如下:

我的单词列表基本上是英语词典的简缩版。通过使用正则表达式,我已经能够删除所有专有名词、缩写、带重音符号的单词,以及所有少于4个字母的单词。我将使用这个词典来制作一个JavaScript单词游戏。(是的,这确实是一个作业,但它不是为了学校学分,这个作业只是为了制作一个简单的JavaScript游戏。我的游戏逻辑是可行的,我也可以手动编辑单词列表,但我希望在2016年前完成,所以正则表达式似乎是最快的方式)。

这个游戏的目的是迫使你的对手完成拼写一个单词。玩家轮流在一个字母串中添加字母,一旦字母串与字典中的某个单词匹配,游戏就结束了。出于这个原因,像 overblown、overhead 和 overkill 这样的单词是负担。一旦 overhead 中的 over 被拼出来,游戏就……嗯……结束了。

我会将 wordList 作为数组加载到一个 JavaScript 文件中,因此我希望它尽可能小。

我确定还有其他方法可以做到这一点(API之类的),但我们在这个作业中不能使用它们。

任何帮助都将不胜感激!

答案#1

存储单词列表的一种高效结构是前缀树。例如,给定一个像这样的字典

'car',
'card',
'carder',
'care',
'cared',
'cares',
'caring',
'can'

这个字典树可能看起来像这样

(其中 0 表示单词的结尾)。

构建字典树的代码相当简单:

function buildTree(words) {
    var tree = {};
    words.forEach(function (word) {
        var t = tree;
        [].forEach.call(word + "0", function (char) {
            t = t[char] || (t[char] = {});
        });
    });
    return tree;
}

现在,要列出所有以给定前缀开头的单词,只需递归地遍历字典树并收集匹配的单词:

function findWords(prefix, tree) {
    var found = [];

    function walk(pfx, t, word) {
        if (!pfx) {
            if (t[0])
                found.push(word)
            for (var c in t)
                walk("", t[c], word + c);
        } else if (pfx[0] in t)
            walk(pfx.substr(1), t[pfx[0]], word + pfx[0]);
    }

    walk(prefix, tree, "");
    return found;
}

完整代码:

function buildTree(words) {
    var tree = {};
    words.forEach(function (word) {
        var t = tree;
        [].forEach.call(word + "0", function (char) {
            t = t[char] || (t[char] = {});
        });
    });
    return tree;
}

function findWords(prefix, tree) {
    var found = [];

    function walk(pfx, t, word) {
        if (!pfx) {
            if (t[0])
                found.push(word)
            for (var c in t)
                walk("", t[c], word + c);
        } else if (pfx[0] in t)
            walk(pfx.substr(1), t[pfx[0]], word + pfx[0]);
    }

    walk(prefix, tree, "");
    return found;
}

words = [
    'car',
    'card',
    'carder',
    'care',
    'cared',
    'cares',
    'caring',
    'can'

]

prefixTree = buildTree(words);
document.write(findWords("care", prefixTree));

要删除以另一个单词开头的单词,你可以像上面那样构建字典树,然后遍历它,一旦找到终止标记(0)就停止搜索:

function buildTree(words) {
    var tree = {};
    words.forEach(function (word) {
        var t = tree;
        [].forEach.call(word + "0", function (char) {
            t = t[char] || (t[char] = {});
        });
    });
    return tree;
}


function findShortWords(tree) {
    var found = [];

    function walk(t, word) {
        if(t[0]) {
            found.push(word);
            return;
          }
        for (var c in t)
            walk(t[c], word + c);
    }

    walk(tree, "");
    return found;
}

words = [
    'card',
    'carder',
    'care',
    'cared',
    'cares',
    'caring',
    'can',
    'canoe',
    'bald',
    'balder',
    'balding',
    'foo'

]

prefixTree = buildTree(words);

document.write(findShortWords(prefixTree));

答案#2

如果你能先对你的单词列表进行逆序排序,那么有一个正则表达式可能对你有帮助。到目前为止,我只在 iPod 上的 JavaScript-replace 中测试过它,但我相信 Sublime Text 也应该能够做到同样的事情。这里是我的示例文本(添加了一些更多的单词来增加趣味性...):

var s="runs,running,runner,run,forging,forgetting,forgets,forget,forged,forge,caring,cares,cared,care,balding,balder,bald"

我将很快把它转换成由换行符分隔的单词列表,然后应用我的正则表达式:

s=s.replace(/,/g,'\n');
s.replace(/(\w+)\w+\n(?=(?:.*\n)*\1(?:\n|$))/g,'');

这将完全删除所有“多余”的行,并为你获取

run
forge
caring
care
bald

然而,如果你仍然希望在不需要的单词位置保留空行,你可以这样做

s.replace(/(\w+)\w+(?=\n(?:.*\n)*\1(?:\n|$))/g,'');

倒序单词顺序对于我的方法至关重要,因为我正在使用可变长度的前瞻来查找单词的基本形式,同时我可能在主模式 (\w+)\w+ 中捕获它的一个变形形式。有趣的是,我从未相信它会这样工作,但它确实如此!这一行的捕获与非捕获部分没有明确定义,但正则表达式引擎会尝试不同的长度,直到在前瞻模式 \n(?:.*\n)*\1(?:\n|$) 中找到捕获组。在我的 JavaScript 示例中,只需设置 RegExp 的 g 标志即可。在 Sublime Text 中,你很可能还需要设置与 m 相当的多行标志。

正如你可能已经注意到的,整个方法仍然存在一个根本性的缺陷:词干“forge”不仅抹去了它自身的其他动词形式,还带走了“forget”的所有形式。这是一个“逻辑”问题,源于在一个单纯的单词列表中,从未明确定义一个动词的基本形式。

更新

刚刚又读了一遍你的问题。对于你的游戏来说,所谓的“根本缺陷”实际上是你想要的东西。所以就忽略我上一段话吧。;-)