Как с помощью Perl показать контекст поискового запроса в результатах поиска?

Я пишу сценарий Perl, который ищет термин в больших частях текста. То, что я хотел бы отобразить пользователю, — это небольшое подмножество текста вокруг условия поиска, чтобы пользователь мог иметь контекст того, где используется это условие поиска. Результаты поиска Google — хороший пример того, чего я пытаюсь добиться, когда контекст вашего поискового запроса отображается под заголовком ссылки.

Мой основной поиск использует это:

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

($title содержит заголовок элемента, в котором был найден поисковый запрос) Это слишком много, поскольку иногда $text будет содержать сотни строк текста.

Это будет отображаться в Интернете, поэтому я мог бы просто указать заголовок в качестве ссылки на фактический текст, но для пользователя нет контекста.

Я попытался изменить свое регулярное выражение, чтобы захватить 4 слова до и 4 слова после поискового запроса, но столкнулся с проблемами, если поисковый запрос находился в самом начале или в самом конце $text.

Что было бы хорошим способом сделать это? Я попытался найти CPAN, потому что я уверен, что у кого-то есть модуль для этого, но я не могу придумать правильные условия для поиска. Я бы хотел сделать это без модулей, если это возможно, потому что установить модули здесь проблематично. У кого-нибудь есть идеи?


person BrianH    schedule 05.03.2009    source источник
comment
Хммм - я нашел Search::Tools::HiLiter (search.cpan.org/~karman/Search-Tools-0.22/lib/Search/Tools/), но он кажется немного громоздким и не таким гибким... Он делает то, что я хочу хотя.   -  person BrianH    schedule 05.03.2009
comment
как выглядело ваше регулярное выражение, когда вы пытались захватить 4 слова до/после?   -  person denkfaul    schedule 05.03.2009
comment
Хммм - я убрал это из кода, так что мне пришло в голову, что я сделал что-то вроде /(\S+\s+){1,4}($search)(\S+\s+){1,4} /   -  person BrianH    schedule 05.03.2009
comment
На самом деле, это, вероятно, было {0,4} в фигурных скобках. Но я думаю, что как-то это опустило слова...   -  person BrianH    schedule 05.03.2009


Ответы (4)


Ваша первоначальная попытка написать 4 слова до/после была не так уж и далека.

Пытаться:

if ($text =~ /((\S+\s+){0,4})($search)((\s+\S+){0,4})/i) {
    my ($pre, $match, $post) = ($1, $3, $4);
    ...
}
person denkfaul    schedule 05.03.2009
comment
Хорошо, теперь это работает отлично, но занимает очень много времени. Используя те же данные, мой (который не возвращает правильных результатов :)) выполняется менее чем за 1 секунду. Я изменил код на ваш фрагмент, и он проработал еще 15 секунд... Есть предположения, как улучшить производительность? - person BrianH; 05.03.2009
comment
if ($text =~ /((\S+\s+){0,4})($search)((\S+\s+){0,4})/ ) { print $1$3$4\n; } Это дает правильный вывод, и он летает. Большое спасибо за Вашу помощь! - person BrianH; 05.03.2009
comment
Я в основном удалил ?: - не знаю, почему это снижает производительность, чтобы они были, хотя... - person BrianH; 05.03.2009
comment
Оооо, извините, это был не ?: - я как-то удалил /i с конца. Мой поиск выполнялся быстро, потому что он выполнялся с учетом регистра. Когда я снова добавляю /i в конец, производительность значительно снижается. Ваше оригинальное решение работает отлично! - person BrianH; 05.03.2009
comment
Итак, теперь мне нужно выяснить, как выполнить это сопоставление без учета регистра и при этом быть быстрым... - person BrianH; 05.03.2009
comment
похоже, что он работает с ?: или без него, он просто создает другую совпадающую переменную, если вы этого не сделаете. Я оставлю это как есть, если кто-то не сможет объяснить, что лучше в этом случае :) - person denkfaul; 05.03.2009
comment
Извините, что сбиваю с толку - мой 4-й комментарий объясняет, что на самом деле вы выполняли совпадение без учета регистра (которое я хочу), что вызывало медлительность. Если я ищу только термин без слов вокруг него, совпадения без учета регистра выполняются очень быстро. - person BrianH; 05.03.2009

Вы можете использовать $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";
person daotoad    schedule 05.03.2009
comment
Хм, это на самом деле работает отлично, даже когда я включаю соответствие без учета регистра... - person BrianH; 05.03.2009
comment
Обратный трюк для захвата с конца строки был взят из сообщения на Perlmonks под названием sexeger - perlmonks.org/index.pl?node_id=33410 - person daotoad; 06.03.2009
comment
Использование специальных переменных $` и $' приводит к снижению производительности для ВСЕХ регулярных выражений, используемых где-либо в программе. Смотрите ответы Шломифа для лучшего способа. - person user55400; 06.03.2009

Я бы предложил использовать позиционные параметры - @+ и @- (см. perldoc perlvar), чтобы найти позицию в строке совпадения и сколько это занимает.

person Shlomi Fish    schedule 05.03.2009
comment
+1. Это лучший ответ, имхо. Он не выполняет никакого ненужного сопоставления с реальным «совпадением» и не подвергается снижению производительности из-за использования $` и $'. - person user55400; 06.03.2009
comment
@ user55400: @+ и @- вернут индексы в строке, поэтому для извлечения значимых слов потребуется дополнительная обработка (в противном случае фиксированное количество символов с большей вероятностью, чем не будет разбиваться на слова вокруг совпадения). - person Dan Dascalescu; 06.03.2014

Вы можете попробовать следующее:

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;

}

Некоторый код явно опущен, но это должно дать вам представление о подходе.

Что касается извлечения заголовка... Я думаю, что этот подход не очень подходит для этого.

person jonstjohn    schedule 05.03.2009