2016-10-09 3 views
1

Для фильтра файлов я хочу использовать массив слов, где строки проверяются, если они соответствуют любому из слов.Perl, строка поиска для появления элементов массива

У меня уже есть довольно простой подход к этому (только существенная часть соответствия):

# check if any of the @words is found in $term 

@words= qw/one 
two 
three/; 
$term= "too for the show"; 

# the following looks very C like 

$size= @words; 
$found= 0; 

for ($i= 0; $i<$size && !$found; $i++) { 
    $found|= $term=~ /$words[$i]/; 
} 

printf "found= %d\n", $found; 

Просмотрев много загадочного синтаксиса и решений в Perl, мне интересно, если (или, вернее, что) это более компактные способы написания этого.

ответ

3

Создать регулярное выражение из всех слов, и сделать только один матч:

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

my @words = qw(one two three); 

my $regex = join '|', map quotemeta, @words; 

for my $term ('too for the show', 'five four three', 'bones') { 
    my $found = $term =~ $regex; 
    printf "found = %d\n", $found; 
} 

Matching /\b(?:$regex)\b/ бы предотвратить bones от соответствия one.

+0

[ 'list2re' из данных :: Munge] (https://metacpan.org/pod/Data::Munge#list2re-LIST) делает что-то очень похоже, но также обрабатывает несколько крайних случаев , – melpomene

+0

+1 от OP. Мне нравится этот (он вроде показывает араканские пути perl, которые я ожидал). Но тот, у кого есть сборка, лучше подходит мне. Спасибо за ответ. – Terminality

2

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

Regexp :: Assemble предпочтительнее делать это вручную. У этого есть полный API вещей, которые вы можете захотеть сделать с таким регулярным выражением, он может обрабатывать граничные случаи, и он может разумно скомпилировать в более эффективное регулярное выражение.

Например, эта программа производит (?^:\b(?:t(?:hree|wo)|one)\b), что приведет к меньшему обратному отклонению. Это становится ОЧЕНЬ важным, поскольку ваш список слов увеличивается по размеру. Последние версии Perl, около 5.14 и выше, сделают это для вас.

use strict; 
use warnings; 
use v5.10; 

use Regexp::Assemble; 

# Wrap each word in \b (word break) so only the full word is 
# matched. 'one' will match 'money' but '\bone\b' won't. 
my @words= qw(
    \bone\b 
    \btwo\b 
    \bthree\b 
); 

# These lines simulate reading from a file. 
my @lines = (
    "won for the money\n", 
    "two for the show\n", 
    "three to get ready\n", 
    "now go cat go!\n" 
); 

# Assemble all the words into one regex. 
my $ra = Regexp::Assemble->new; 
$ra->add(@words); 

for my $line (@lines) { 
    print $line if $line =~ $ra; 
} 

отметить также foreach style loop to iterate over an array и использование statement modifier.

Наконец, я использовал \b, чтобы гарантировать соответствие только фактических слов, а не подстрок, таких как money.

+0

Современные версии perl будут скомпилировать 'one | two | three' в trie внутри, что не приведет к отступлению. – melpomene

+0

@melpomene Да, стоит упомянуть. Благодарю. Это очень хорошая оптимизация, но теперь у вас есть зависимость от версии Perl (я хочу сказать [это стало стабильным около 5.14] (http://perldoc.perl.org/5.14.0/perldelta.html#Regular- Expression-Bug-Fixes)?) И ​​я не уверен, насколько сложным может быть регулярное выражение. Я бы не стал полагаться на него для чего-то, чья производительность критически зависит от оптимизации регулярных выражений. И Regexp :: Assemble решает так много других проблем, что это все еще стоит. – Schwern

+0

Я уже требую 5.10+ для других вещей, так что это не большая проблема для меня, но точка взята. – melpomene

2

Это, пожалуй, слишком упрощенный «перевод» вашего кода типа C в perl.

  • Pro: Это компактный
  • Con: Это не очень эффективно (другие ответы тонна лучше здесь).
@words= qw/one 
two 
three/; 
$term= "too for the show"; 

my @found = grep { $term =~ /$_/; } @words; 

printf "found= %d\n", scalar @found; 
+1

Если вам просто нужен счет, 'my $ count = grep {$ term = ~/$ _ /} @ words' также работает. – melpomene

+0

+1 от OP. Мне нравится этот (он вроде показывает араканские пути perl, которые я ожидал). Но тот, у кого есть сборка, лучше подходит мне. Спасибо за ответ. – Terminality

Смежные вопросы