Самый простой способ сопоставить массив строк для поиска в perl?

Что я хочу сделать, это проверить массив строк по моей строке поиска и получить соответствующий ключ, чтобы я мог его сохранить. Есть ли волшебный способ сделать это с Perl, или я обречен на использование цикла? Если да, то каков наиболее эффективный способ сделать это?

Я относительно новичок в Perl (я написал только два других скрипта), поэтому я пока не знаю много волшебства, просто Perl is magic = D

Reference Array: (1 = 'Canon', 2 = 'HP', 3 = 'Sony')
Search String: Sony Cyber-shot DSC-S600
End Result: 3

Ответ 1

UPDATE:

Основываясь на результатах обсуждения в этом вопросе, в зависимости от ваших намерений/критериев того, что составляет "не используя цикл", решение map ниже (см. " Вариант № 1), может быть самым кратким решением при условии, что вы не рассматриваете цикл map (короткая версия ответов: это цикл в отношении реализации/производительность, это не цикл с теоретической точки зрения).


Предполагая, что вам не важно, получите ли вы "3" или "Sony" в качестве ответа, вы можете сделать это без цикла в простом случае, создав регулярное выражение с помощью "или" логика (|) из массива, например:

my @strings = ("Canon", "HP", "Sony"); 
my $search_in = "Sony Cyber-shot DSC-S600"; 
my $combined_search = join("|",@strings); 
my @which_found = ($search_in =~ /($combined_search)/); 
print "$which_found[0]\n";

Результат моего тестового прогона: Sony

Регулярное выражение будет (после того, как переменная $combined_search будет интерполирована Perl) примет форму /(Canon|HP|Sony)/, которую вы хотите.

Это НЕ будет работать как есть, если какая-либо из строк содержит специальные символы регулярных выражений (например, | или )) - в этом случае вам нужно избежать их

ПРИМЕЧАНИЕ. Я лично считаю это несколько обманом, потому что для реализации join() Perl сам должен делать петлю где-то внутри интерпретатора. Таким образом, этот ответ может не удовлетворить вашему желанию оставаться без петли, в зависимости от того, хотите ли вы избежать цикла для соображений производительности, чтобы иметь более чистый или более короткий код.


P.S. Чтобы получить "3" вместо "Sony", вам придется использовать цикл - либо очевидным образом, выполнив 1 совпадение в цикле под всем этим; или с помощью библиотеки, которая избавляет вас от написания цикла самостоятельно, но будет иметь петлю под вызовом.

Я предоставлю 3 альтернативных решения.

# 1: - мой любимый. Использует "карту", ​​которую я лично все еще рассматриваю как цикл:

my @strings = ("Canon", "HP", "Sony"); 
my $search_in = "Sony Cyber-shot DSC-S600"; 
my $combined_search = join("|",@strings); 
my @which_found = ($search_in =~ /($combined_search)/); 
print "$which_found[0]\n";
die "Not found" unless @which_found;
my $strings_index = 0;
my %strings_indexes = map {$_ => $strings_index++} @strings;
my $index = 1 + $strings_indexes{ $which_found[0] };
# Need to add 1 since arrays in Perl are zero-index-started and you want "3"

# 2 опция: использует цикл, скрытый за хорошим библиотечным методом CPAN:

use List::MoreUtils qw(firstidx);
my @strings = ("Canon", "HP", "Sony"); 
my $search_in = "Sony Cyber-shot DSC-S600"; 
my $combined_search = join("|",@strings); 
my @which_found = ($search_in =~ /($combined_search)/); 
die "Not Found!"; unless @which_found;
print "$which_found[0]\n";
my $index_of_found = 1 + firstidx { $_ eq $which_found[0] } @strings; 
# Need to add 1 since arrays in Perl are zero-index-started and you want "3"

# 3: Здесь очевидный цикл:

my $found_index = -1;
my @strings = ("Canon", "HP", "Sony"); 
my $search_in = "Sony Cyber-shot DSC-S600"; 
foreach my $index (0..$#strings) {
    next if $search_in !~ /$strings[$index]/;
    $found_index = $index;
    last; # quit the loop early, which is why I didn't use "map" here
}
# Check $found_index against -1; and if you want "3" instead of "2" add 1.

Ответ 2

Вот решение, которое создает регулярное выражение со встроенным кодом для увеличения индекса, поскольку perl перемещается по регулярному выражению:

my @brands = qw( Canon HP Sony );
my $string = "Sony Cyber-shot DSC-S600";

use re 'eval';  # needed to use the (?{ code }) construct

my $index = -1;
my $regex = join '|' => map "(?{ \$index++ })\Q$_" => @brands;

print "index: $index\n" if $string =~ $regex;

# prints 2 (since Perl array indexing starts with 0)

Строка, которая добавляется к каждой марки, сначала увеличивает индекс, а затем пытается сопоставить бренд (экранированный с quotemeta (как \Q), чтобы разрешать специальные символы регулярных выражений в торговых марках).

Когда совпадение не выполняется, движок регулярных выражений проходит мимо чередования |, а затем повторяется шаблон.

Если вам нужно сопоставить несколько строк, обязательно перед reset $index. Или вы можете добавить (?{$index = -1}) к строке регулярного выражения.

Ответ 3

Простым способом является использование хэша и регулярного выражения:

my $search = "your search string";
my %translation = (
    'canon' => 1,
    'hp'    => 2,
    'sony'  => 3
);

for my $key ( keys %translation ) {
    if ( $search =~ /$key/i ) {
        return $translation{$key};
    )
}

Естественно, возвращение может быть также легко напечатано. Вы также можете окружить всю вещь в цикле while:

while(my $search = <>) {
    #your $search is declared = to <> and now gets its values from STDIN or strings piped to this script
}

Также обратите внимание на функции регулярного выражения perl на perlre и взглянуть на структуры данных perl на perlref

ИЗМЕНИТЬ

как только что указывалось мне, вы пытались уклониться от использования цикла. Другим методом было бы использовать функцию отображения perl. Посмотрите здесь.

Ответ 4

Вы также можете взглянуть на Regexp::Assemble, который возьмет коллекцию подрепрексов и построит одно суперрежимное выражение из них, которые затем могут быть использованы для тестирования для всех из них сразу (и дает вам текст, который соответствовал регулярному выражению, конечно). Я не уверен, что это лучшее решение, если вы смотрите только на три строки/регулярные выражения, которые вы хотите сопоставить, но это определенно способ пойти, если у вас есть значительно больший целевой набор - проект, который я изначально использовал на нем имеет библиотеку из примерно 1500 терминов, которые соответствуют ей, и она работает очень хорошо.