从较小的String中查找大量字符串中的所有匹配项

时间:2013-03-28 19:26:13

标签: algorithm text substring

我有一大堆单词和短语(词典或词典),其中包括通配符。我需要在一个更小的字符串中找到这些单词和短语的所有实例(目前约150个字符)。

最初,我想反向运行操作;那就是检查我的小字符串中的每个单词是否都存在于Lexicon中,这可以作为哈希表来实现。问题是我的Lexicon中的某些值不是单个单词,而且很多都是通配符(例如substri *)。

我正在考虑使用Rabin-Karp algorithm,但我不确定这是最佳选择。

执行此操作的有效算法或方法是什么?

示例数据

字典包含数百个单词,可以扩展。这些单词可能以通配符(星号)结尾。以下是一些随机的例子:

  • 释放*
  • 粗心*
  • 很大的损失

我们正在分析的文本(此时)是简短的,非正式的(语法方面的)英语陈述。文本的主要示例(同样,此时)将是Twitter推文。这些仅限于140个字符。例如:

Just got the Google nexus without a contract. Hands down its the best phone 
I've ever had and the only thing that could've followed my N900.

虽然注意到我们对此文本执行非常简单的情感分析可能会有所帮助;我们的情感分析技术我的担忧。我只是将现有解决方案迁移到“实时”处理系统,需要执行一些优化。

7 个答案:

答案 0 :(得分:4)

我认为这是Aho-Corasick string-matching algorithm的一个很好的用例,它专门用于在单个字符串中查找大量字符串的所有匹配项。它分两个阶段运行 - 第一阶段,其中创建匹配的自动机(可以提前完成并且仅需要线性时间),以及第二阶段,其中自动机用于查找所有匹配(仅需要线性时间) ,加上与比赛总数成比例的时间)。该算法也可以适用于支持通配符搜索。

希望这有帮助!

答案 1 :(得分:3)

我想抛弃的一个答案是Boyer-Moore搜索算法。它是grep使用的算法。 Grep可能是最快的搜索工具之一。此外,您可以使用GNU Parallel之类的东西让grep并行运行,从而真正加速算法。

此外,这里有一个您可能感兴趣的good article

答案 2 :(得分:2)

您仍然可以使用原始想法,检查文本中的每个单词与字典。但是,为了运行有效,您需要索引字典,以便快速进行查找。 信息回溯系统中使用的技巧是存储所谓的 permuterm索引http://nlp.stanford.edu/IR-book/html/htmledition/permuterm-indexes-1.html)。

基本上你要做的是在字典中存储每个可能的单词排列(例如房子):

house$
ouse$h
use$ho
...
e$hous

然后,可以使用此索引快速检查通配符查询。例如,如果您有问题,则可以查看以e$ho开头的术语的permuterm索引,并且您很快就会找到与房屋匹配的内容。

搜索本身通常使用一些对数搜索策略(二分搜索或b树),因此通常非常快。

答案 3 :(得分:2)

只要模式是完整的单词:您不希望orstorage匹配;空格和标点符号是匹配锚点,然后一个简单的方法是将您的词典转换为扫描仪生成器输入(例如,您可以使用flex),生成扫描仪,然后在输入上运行它。

扫描仪生成器用于识别输入中令牌的出现,其中每个令牌类型由正则表达式描述。 Flex和类似程序可以快速创建扫描仪。 Flex默认处理多达8k规则(在您的情况下是词典条目),这可以扩展。生成的扫描仪以线性时间运行,实际上非常快。

在内部,令牌正则表达式在标准的“Kleene定理”管道中进行转换:首先是NFA,然后是DFA。然后将DFA转换为其独特的最小形式。这是在HLL表中编码的,该表在包装器内部发出,该包装器通过引用该表来实现扫描器。这就是flex的作用,但其他策略也是可行的。例如,DFA可以转换为goto代码,其中DFA状态由代码运行时的指令指针隐式表示。

空间作为锚点的原因是由Flex等程序创建的扫描程序通常无法识别重叠匹配:strangers无法与strangersrange匹配,例如。

这是一个灵活的扫描程序,它匹配您提供的示例词典:

%option noyywrap
%%
"good"                    { return 1; }
"bad"                     { return 2; }
"freed"[[:alpha:]]*       { return 3; }
"careless"[[:alpha:]]*    { return 4; }
"great"[[:space:]]+"loss" { return 5; }
.                         { /* match anything else except newline */ }
"\n"                      { /* match newline */ }
<<EOF>>                   { return -1; }
%%
int main(int argc, char *argv[])
{
  yyin = argc > 1 ? fopen(argv[1], "r") : stdin;
  for (;;) {
    int found = yylex();
    if (found < 0) return 0;
    printf("matched pattern %d with '%s'\n", found, yytext);
  }
}

并运行它:

$ flex -i foo.l
$ gcc lex.yy.c
$ ./a.out
Good men can only lose freedom to bad
matched pattern 1 with 'Good'
matched pattern 3 with 'freedom'
matched pattern 2 with 'bad'
through carelessness or apathy.
matched pattern 4 with 'carelessness'

答案 4 :(得分:1)

这不完全回答算法问题,但请查看re2库。 Python,Ruby和各种其他编程语言都有很好的接口。根据我的经验,它是盲目快速的,并且在我的代码中删除了相当类似的瓶颈,我没有那么大惊小怪,几乎没有额外的代码。

唯一的复杂性来自重叠模式。如果您希望模式从单词边界开始,您应该能够将字典分成r_1, r_2, ..., r_k形式的一组正则表达式\b(foobar|baz baz\S*|...),其中r_{i+1}中的每个组都有一个前缀r_i。然后,您可以进行短路评估,因为如果r_{i+1}匹配,那么r_i必须匹配。

除非你在高度优化的C中实现你的算法,否则我敢打赌,这种方法比其他任何(算法优越的)答案更快

答案 5 :(得分:0)

让我直截了当。您有大量查询和一个小字符串,并且您希望在该字符串中查找所有这些查询的实例。

在这种情况下,我建议您将这个小文档编入索引,以便您的搜索时间尽可能短。地狱。使用该文档大小,我甚至会考虑进行小型突变(以匹配通配符等),并将它们编入索引。

答案 6 :(得分:-1)

我的任务非常相似。 这就是我如何解决它,表现令人难以置信 http://www.foibg.com/ijita/vol17/ijita17-2-p03.pdf