【问题标题】:Using Perl, how do I show the context around a search term in the search results?使用 Perl,如何在搜索结果中显示搜索词周围的上下文?
【发布时间】:2010-10-11 14:10:17
【问题描述】:

我正在编写一个在大部分文本中搜索术语的 Perl 脚本。我想向用户显示的是搜索词周围文本的一小部分,因此用户可以了解使用此搜索词的位置的上下文。谷歌搜索结果是我试图完成的一个很好的例子,你的搜索词的上下文显示在链接的标题下。

我的基本搜索使用这个:

if ($text =~ /$search/i ) {
    print "${title}:${text}\n";
}

($title 包含在其中找到搜索词的项目的标题) 不过这太多了,因为有时 $text 会包含数百行文本。

这将显示在网络上,所以我可以只提供标题作为实际文本的链接,但没有用户上下文。

我尝试修改我的正则表达式以捕获搜索词之前的 4 个词和之后的 4 个词,但如果搜索词位于 $text 的开头或结尾,则会遇到问题。

什么是完成此任务的好方法?我尝试搜索 CPAN,因为我确定有人为此提供了一个模块,但我想不出要搜索的正确术语。如果可能,我希望喜欢在没有模块的情况下执行此操作,因为在这里安装模块很痛苦。有人有什么想法吗?

【问题讨论】:

  • 嗯 - 我找到了 Search::Tools::HiLiter (search.cpan.org/~karman/Search-Tools-0.22/lib/Search/Tools/…),但它看起来有点笨重而且不够灵活......不过它可以满足我的需求。
  • 当您尝试在之前/之后捕获 4 个单词时,您的正则表达式是什么样的?
  • 嗯 - 我把它从代码中去掉了,所以我想我做了类似 /(\S+\s+){1,4}($search)(\S+ \s+){1,4}/
  • 其实大括号里应该是{0,4}。但我认为它以某种方式遗漏了文字......

标签: perl search full-text-search


【解决方案1】:

您可以使用 $and $' to get the string before and after the match. Then truncate those values appropriately. But as blixtor points out, shlomif is correct to suggest using@+and@-to avoid the performance penalty imposed by $ 和 #' -

$foo =~ /(match)/;

my $match = $1;
#my $before = $`;
#my $after = $';
my $before = substr($foo, 0, $-[0]);
my $after =  substr($foo, $+[0]);

$after =~ s/((?:(?:\w+)(?:\W+)){4}).*/$1/;
$before = reverse $before;                   # reverse the string to limit backtracking.
$before =~ s/((?:(?:\W+)(?:\w+)){4}).*/$1/;
$before = reverse $before;

print "$before -> $match <- $after\n";

【讨论】:

  • 嗯 - 这实际上表现得很好,即使我打开不区分大小写的匹配...
  • 从字符串后面抓取的反向技巧来自 Perlmonks 上名为 sexeger 的帖子 - perlmonks.org/index.pl?node_id=33410
  • 使用特殊变量 $` 和 $' 会导致程序中任何地方使用的所有正则表达式的性能损失。请参阅 shlomif 的答案以获得更好的方法。
【解决方案2】:

我建议使用位置参数 - @+ 和 @-(参见 perldoc perlvar)来查找匹配字符串中的位置,以及它需要多少。

【讨论】:

  • +1。这是最好的答案,恕我直言。它不会围绕真正的“匹配”进行任何不必要的匹配,也不会因使用 $` 和 $' 而导致性能损失。
  • @user55400:@+ 和@- 将返回字符串中的索引,因此需要额外的处理来提取有意义的单词(否则,固定数量的字符更有可能分解成单词比赛前后)。
【解决方案3】:

您最初尝试使用之前/之后的 4 个单词并不算太远。

试试:

if ($text =~ /((\S+\s+){0,4})($search)((\s+\S+){0,4})/i) {
    my ($pre, $match, $post) = ($1, $3, $4);
    ...
}

【讨论】:

  • 好的,现在可以完美运行了,但是需要非常很长时间。使用相同的数据,我的(它不会返回正确的结果:))在不到 1 秒的时间内运行。我将代码更改为您的代码片段,它运行了 15 秒以上...您有关于如何提高性能的猜测吗?
  • if ($text =~ /((\S+\s+){0,4})($search)((\S+\s+){0,4})/ ) { print " $1$3$4\n";这会产生正确的输出,并且它会飞。非常感谢您的帮助!
  • 我基本上删除了 ?: - 不知道为什么这会降低性能,但...
  • 哦-抱歉-不是?:-不知何故我从最后删除了/i。我的搜索运行得很快,因为它区分大小写。当我在最后添加 /i 时,性能会降低方式。您的原始解决方案完美运行!
  • 所以现在我需要弄清楚如何执行这种不区分大小写的匹配,并且仍然很快......
【解决方案4】:

您可以尝试以下方法:

if ($text =~ /(.*)$search(.*)/i ) {

  my @before_words = split ' ', $1;
  my @after_words = split ' ',$2;

  my $before_str = get_last_x_words_from_array(@before_words);
  my $after_str = get_first_x_words_from_array(@after_words); 

  print $before_str . ' ' . $search . ' ' . $after_str;

}

一些代码显然被省略了,但这应该让您对方法有所了解。

至于提取标题...我认为这种方法不太适合。

【讨论】:

    猜你喜欢
    • 2012-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-12
    • 1970-01-01
    • 2019-03-01
    • 1970-01-01
    • 2018-03-05
    相关资源
    最近更新 更多