搜索多个字符串

时间:2014-02-11 16:37:46

标签: algorithm search full-text-search complexity-theory

我知道在文件中查找一个字符串的有效方法(kmp),或文件中的各种字符串(trie)

但是,多年来,我一直想知道是否有一种方法(并且在某种程度上认为不可能)搜索多个文件中的多个字符串

假设我有一百万个文件,我想回答诸如“查找具有字符串”香蕉“,”摩托艇“和”白狐“的文件”的查询。什么是有效的算法?有吗?

当然,可以在线性时间内搜索要搜索的文件大小。但对于大量的大文件来说,这似乎是不可行的。 谷歌的存在似乎表明实际上有一个非常快的算法来做到这一点。甚至可能是每个查询只取决于查询大小,而不是文本大小的数据库(当然,这样的算法会涉及输入文件的一些预处理)

我认为必须有一个这样的算法(谷歌做到了!)但我的搜索没有找到任何结果。

5 个答案:

答案 0 :(得分:3)

并行编程

这绝对是并行编程的一项任务:将文件分发到不同的计算单元,让它们进行搜索,然后收集结果。这实际上是谷歌所做的,例如他们通过结合千种商用硬件PC解决了一些翻译问题。 (尽管他们可能正在使用其他硬件来获取真实的Google搜索结果。)您可以阅读热门文章on the internet

“MapReduce”作为一个概念

谷歌发明了一个名为MapReduce, which they wrote down in a whitepaper的范例。这基本上归结为在第一步中将输入映射到输出(广泛分布)。然后在第二步中将所有小结果减少为一个主要结果。

可以像这样实现搜索:

  • 地图:将文档与要搜索的关键字一起分发。如果在当前文件中找到搜索词,则从计算节点返回文件名。否则什么也不返回。
  • reduce :从所有节点收集列表中的所有文件名。

(这与他们在论文中提出的“分布式grep”问题几乎相同。)

查找给定文本中是否存在给定字符串的问题在名称“字符串匹配”下进行了充分研究,例如参见the Rabin-Karp algorithmKnuth-Morris-Karp algorithm (只是为了得到任何东西)。因此 map 的实现相当容易。

对于文件的分发,可以使用许多不同的技术。如果想要了解分布式文件系统的可能性,可以收集有关Google文件系统(GFS)的信息,e.g. in the corresponding whitepaper

reduce 几乎什么都不做,所以这很容易。

成品。

这是MapReduce范例的最佳优势:一旦理解了map和reduce如何结合到一个结果,实现这两个功能就相当容易了。如果之前实现了MapReduce框架,那么人们就不必担心计算的并行性 - 否则会导致严重的头痛。

其他概念

这绝对不是唯一可能的概念。

  • 可以根据您使用的硬件而有所不同(像MapReduce这样的独立PC,或者更像是拥有数十个CPU的超级计算机)。
  • 可以改变您使用的分布式(或非分布式)文件系统。
  • 可以改变编程语言,这也可以产生巨大的差异。

如果你对这个研究领域感兴趣,你会发现很多其他的可能性,我相信在不久的将来会出现更多,因为分布式系统比以往任何时候都出现,但我希望我能提供一些见解在什么是可能的,注意什么,甚至是如何立即实现这一目标的方向。

答案 1 :(得分:2)

(这个问题的措辞相当广泛。任何有效的解决方案都高度依赖于所做出的具体假设。为了讨论起见,我将做一些你没有明确提及的假设。)

模型

让我们说......

  • f个文件,
  • 这些文件中总共
  • w个字,
  • d个唯一字词(d是覆盖所有文件所需字典的最小尺寸),
  • 查询中的
  • q个字词和
  • r个文件位于查询的结果集中。

我会假设q<< d<< f<< w(即每个变量是'比其后继者小的数量级'),并且q基本上是常数,即O(1)。我还假设您主要关心的是最小化在O(f)O(w)中测量的摊销计算时间,您愿意投入更多内存以减少计算时间并且您希望获得经常查询。

请注意,算法的运行时间不能优于O(r),因为我们需要输出属于结果集的每个文件。

算法

根据从单词到文件集的散列映射创建索引,如下所示:

index = {}
for file in files:
  for word in file:
    index[word] += file

此代码在O(w)中运行,这是最小的(因为您需要至少查看一次完整的输入)。要查找包含query中所有字词的所有文件,请运行:

wordWithLeastFilesMatching = min(query, key=lambda word: len(index[word]))
result = set(index[wordWithLeastFilesMatching])
for word in query:
  result = result.intersection(index[word])
return result

此代码的运行时间主要取决于它需要执行的q集交集。在典型情况下,每个集合可能O(log(f))大,并且各个集合的重叠是适中的。在这种情况下,计算需要O(log(f))

然而,在最坏的情况下,即使重叠(因此O(f))很小,每个集合也会r大。在这种情况下,计算仍然需要O(f)

答案 2 :(得分:0)

由于没有其他人回答,我将以简单化的想法开始滚动,并希望聪明的人能够进一步帮助。

好的,首先,只需将1,000,000个文件拆分为多个服务器就可以轻松实现并行化,因为前两个服务器可以独立于剩余的文件进行搜索。

然后每个服务器都可以运行这样的事情,假设您的文档以“.txt”结尾:

#!/bin/bash
find . -name "*.txt" | while IFS= read a
do
  grep -l banana "$a" | while IFS= read b
  do
    grep -l motorboat "$b" | while IFS= read c
    do
      grep -l "the white fox" "$c"
    done
  done
done

通过在常见单词之前搜索更罕见的单词,可以提高性能。

此外,您可以使用awk并传入所有3种搜索模式,并在找到所有搜索模式后立即退出,而不是继续处理直到文件结束。

当然,如果您要进行多次重复查询,则需要花费更多时间将文件加载到高效结构(例如哈希)中。因此,如果您的输入文件包含单词“motorboat”,那么您的哈希中会有一个条目,并且只需通过测试哈希中的存在就可以非常快速地测试文件是否包含该单词。然后,这可以修剪需要进入上述方法的文件,并大大提高性能。

因此,以下代码将解析所有“.txt”文件,并为每个单词记录它所在的文件。因此,当需要进行搜索时,您只需传递搜索条件并找到包含单词的文件(不一定彼此相邻)并将该文件列表传递给上面的脚本:

#!/usr/bin/perl
use strict;
use warnings;

my %words;

# Load all files ending in ".txt"
my @files=<*.txt>;
foreach my $file (@files){
   print "Loading: $file\n";
   open my $fh, '<', $file or die "Could not open $file";
   while (my $line = <$fh>) {
     chomp $line;
     foreach my $str (split /\s+/, $line) {
        $words{$str}{$file}=1;
     }
   }
   close($fh);
}

foreach my $str1 (keys %words) {
  print "Word: \"$str1\" is in : ";
  foreach my $str2 (keys $words{$str1}) {
    print "$str2 ";
  }
  print "\n";
}

我创建的小测试文件的输出如下:

./go
Loading: a.txt
Loading: b.txt
Loading: c.txt
Loading: d.txt
Word: "the" is in : c.txt d.txt 
Word: "motorboat" is in : b.txt d.txt 
Word: "white" is in : c.txt d.txt 
Word: "banana" is in : c.txt d.txt a.txt 
Word: "fox" is in : c.txt d.txt

答案 3 :(得分:0)

如果您可以定期将每个文件序列化为trie,那么您可以根据搜索需要对每个trie进行反序列化,并对所有尝试执行查询?

它会非常快,但当然要求你让一个进程不断更新文件的尝试。我很确定google还会以某种方式对其数据进行索引,你必须做出一些权衡 - 在这种情况下以内存为代价提高性能。

答案 4 :(得分:0)

将每个文件中的文本分成一组词位并捕获与每个词位匹配的文本。将每个lexeme反向索引到匹配文件集。对于每个搜索词,转换为lexeme并返回每个文件中每个匹配的捕获文本。