将字符串拆分为字典单词


Split strings into Dictionary words

我正在寻找PHP中最有效的算法来检查字符串是否仅由字典单词组成。

示例:

thissentencewasmadefromenglishwords
thisonecontainsyxxxyxsomegarbagexaatoo
pure
thisisalsobadxyyyaazzz

输出:

thissentencewasmadefromenglishwords
pure

a.txt

contains the dictionary words

b.txt

contains the strings: one in every line, without spaces made from a..z chars only

另一种方法是使用Aho-Corasick字符串匹配算法。基本的想法是在你的词典中阅读单词,并由此创建Aho-Corasick树结构。然后,通过搜索功能运行每个要拆分为单词的字符串。

这种方法的美妙之处在于,创建树是一次性的成本。然后,您可以将它用于正在测试的所有字符串。搜索函数以O(n)(n是字符串的长度)加上找到的匹配数运行。它真的很有效率。

搜索功能的输出将是一个字符串匹配列表,告诉您哪些单词在什么位置匹配。

维基百科的文章并没有对Aho-Corasick算法做出很好的解释。我更喜欢原始文件,它很容易接近。请参阅"高效字符串匹配:书目搜索的辅助工具"。

例如,给定你的第一个字符串:

thissentencewasmadefromenglishwords

你会得到(部分):

this, 0
his, 1
sent, 4
ten, 7
etc.

现在,按位置对匹配列表进行排序。当你从字符串匹配器中得到它时,它将被几乎排序,但不是完全排序。

一旦列表按位置排序,您要做的第一件事就是确保在位置0处有匹配项。如果没有,则字符串未通过测试。如果存在(并且在位置0处可能有多个匹配),则获取匹配字符串的长度,并查看该位置是否存在字符串匹配。加上比赛的长度,看看下一个位置是否有比赛,等等。

如果你测试的字符串不是很长,那么你可以使用这样的暴力算法。不过,构建一个按位置索引的匹配哈希图会更有效。当然,一个特定的位置可能有多场比赛,所以你必须考虑到这一点。但是,看看在某个位置是否有比赛会很快。

当然,要实现Aho-Corasick算法还需要一些工作。快速的谷歌搜索显示有php实现可用。我不知道他们工作得有多好。

在一般情况下,这应该很快。同样,这取决于你的绳子有多长。但在任何一个位置上的比赛相对较少,这对你有帮助。您可能会构造出病态的糟糕运行时的字符串,但您可能必须非常努力地尝试。再说一遍,如果绳子很短,即使是病理性病例也不会花太长时间。

这是一个可以使用动态编程解决的问题,基于以下公式:

f(0) = true
f(i) = OR { f(i-j) AND Dictionary.contais(s.substring(i-j,i) } for each j=1,...,i

首先,将您的文件加载到字典中,然后使用DP解决方案来执行上述公式。

伪代码类似于:(希望我对索引没有"逐个关闭"。)

check(word):
   f = new boolean[word.length() + 1)
   f[0] = true
   for i from 1 to word.length() + 1:
      f[i] = false
      for j from 1 to i-1:
          if dictionary.contains(word.substring(j-1,i-1)) AND f[j]:
             f[i] = true
   return f[word.length()

我推荐一种递归方法。类似这样的东西:

<?php
    $wordsToCheck = array(
        'otherword',
        'word1andother',
        'word1',
        'word1word2',
        'word1word3',
        'word1word2word3'
    );
    $wordList = array(
        'word1',
        'word2',
        'word3'
    );
    $results = array();
    function onlyListedWords($word, $wordList) {
        if (in_array($word, $wordList)) {
            return true;
        } else {
            $length = strlen($word);
            $wordTemp = $word;
            $part = '';
            for ($i=0; $i < $length; $i++) { 
                $part .= $wordTemp[$i];
                if (in_array($part, $wordList)) {
                    if ($i == $length - 1) {
                        return true;
                    } else {
                        $wordTemp = substr($wordTemp, $i + 1);
                        return onlyListedWords($wordTemp, $wordList);
                    }
                }
            }
        }
    }
    foreach ($wordsToCheck as $word) {
        if (onlyListedWords($word, $wordList))
            $results[] = $word;
    }
    var_dump($results);
?>