【问题标题】:Split strings into Dictionary words将字符串拆分为字典单词
【发布时间】:2014-06-09 14:55:05
【问题描述】:

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

例子:

thissentencewasmadefromenglishwords
thisonecontainsyxxxyxsomegarbagexaatoo
pure
thisisalsobadxyyyaazzz

输出:

thissentencewasmadefromenglishwords
pure

一个.txt

contains the dictionary words

b.txt

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

【问题讨论】:

  • 您真的在寻找最有效的算法吗?或者任何在合理时间内起作用的东西都会起作用?你有可以按单词查询的字典吗?您尝试过什么,或者您不知道如何解决问题?
  • 你好,任何运行速度超过 10-15 分钟的都可以。得到一个包含 100.000 个单词的单词列表,字符串列表约为 50.000。我试图搜索每个字符串中的单词,如果字符串可以用找到的单词组成,那么它就是一个输出字符串。但这不是最快的。

标签: php algorithm substring


【解决方案1】:

这是一个可以使用Dynamic Programming 解决的问题,基于以下公式:

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()

【讨论】:

  • 它会覆盖有问题的字符串吗,比如 smartestonia(smart+Estonia = 好命中或 smartest+onia 坏命中)?
  • @fobiss 是的,当然。这就是DP的重点。这基本上是一种实现详尽搜索的有效方法。
  • 我尝试在 php 中实现您的伪,但效果不佳,我认为 substring 方法可能会造成混淆。在 php substr 中,第三个参数是要返回的字符的长度。伪中的第二个参数是什么意思?
  • @Cyber​​Cube 参数是所需子字符串的开始索引和结束索引。确保索引中也没有“off by 1”错误
【解决方案2】:

我推荐一种递归方法。像这样的:

<?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);
?>

【讨论】:

  • 这基本上是我提出的DP解决方案的低效解决方案(穷举搜索)。为了使其高效(并且对于长度约为 100 的字符串可行),您需要引入自上而下(记忆)DP,或自下而上 DP(我的回答建议第二个,自下而上)。
  • 如果没有上述改进,您将无法运行长度约为 100 的字符串,因为所消耗的时间呈指数增长。
【解决方案3】:

另一种方法是使用Aho-Corasick string matching algorithm。基本思想是阅读您的单词词典并从中创建 Aho-Corasick 树结构。然后,您通过搜索功能运行每个要拆分为单词的字符串。

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

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

维基百科的文章没有很好地解释 Aho-Corasick 算法。我更喜欢原始论文,它非常平易近人。见Efficient String Matching: An Aid to Bibliographic Search

因此,例如,给定您的第一个字符串:

thissentencewasmadefromenglishwords

你会得到(部分):

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

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

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

如果您要测试的字符串不是很长,那么您可以使用类似的蛮力算法。但是,构建匹配的哈希映射会更有效,按位置索引。当然,一个特定位置可能有多个匹配项,因此您必须考虑到这一点。但是查看某个位置是否有匹配会非常快。

当然,实现 Aho-Corasick 算法需要做一些工作。快速谷歌搜索显示有可用的 php 实现。我不知道它们的效果如何。

在一般情况下,这应该非常快。同样,这取决于您的琴弦有多长。但是任何一个位置上的比赛都相对较少,这对您有所帮助。您可能会构建表现出病态运行时差的字符串,但您可能必须非常努力地尝试。再说一次,如果字符串很短,即使是病态的病例也不会花费太长的时间。

【讨论】:

    猜你喜欢
    • 2011-06-12
    • 1970-01-01
    • 2018-05-16
    • 2022-01-18
    • 2011-10-23
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多