2010-06-11 11 views
7

Ce que je veux faire est de vérifier un tableau de chaînes par rapport à ma chaîne de recherche et obtenir la clé correspondante afin que je puisse le stocker. Y a-t-il un moyen magique de faire ça avec Perl, ou suis-je condamné à utiliser une boucle? Si oui, quel est le moyen le plus efficace de le faire?Le moyen le plus simple de faire correspondre un tableau de chaînes à rechercher dans Perl?

Je suis relativement nouveau à Perl (je n'ai écrit 2 autres scripts), donc je ne sais pas beaucoup de la magie encore, il suffit que Perl est magique = D

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

Perl n'est pas vraiment magique. C'est juste un exemple de la technologie avancée d'Arthur C. Clarke qui est indiscernable de la magie :) Puis encore, il y a tout ce que je considère personnellement comme le vaudou :( – DVK

+0

Quoi de neuf avec cette boucle déteste ces derniers temps? Si vous avez besoin de faire quelque chose Sur une liste d'éléments, vous devez les boucler d'une façon ou d'une autre.Vous ne pouvez pas utiliser explicitement 'for' ou' while' mais à la fin de la journée, même la solution la plus ésotérique utilisera un cycle quelconque sous le capot. –

+0

@kemp - Y at-il eu d'autres questions anti-boucle récemment que j'ai raté? – DVK

Répondre

11

MISE à JOUR:

sur la base des résultats des discussions à this question, en fonction de votre intention/critères de ce qui constitue « ne pas utiliser une boucle », la solution map ci-dessous (voir "Option n ° 1) peut être le plus solution concise, à condition que vous ne consi der map une boucle (la version courte des réponses est: c'est une boucle en ce qui concerne l'implémentation/la performance, ce n'est pas une boucle du point de vue théorique du langage).


En supposant que vous ne vous inquiétez pas si vous obtenez « 3 » ou « Sony » comme la réponse, vous pouvez le faire sans une boucle dans un cas simple, en construisant une expression régulière avec « ou » logique (|) à partir du tableau, comme ceci:

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

Résultat de mon essai: Sony

l'expression régulière (une fois la variable $combined_search est interpolée par Perl) ta ke le formulaire /(Canon|HP|Sony)/ qui est ce que vous voulez.

Cela ne fonctionnera pas en l'état si l'une des chaînes contenant des caractères spéciaux regex (tels que | ou )) - dans ce cas, vous devez les échapper

NOTE: Personnellement, je considère cela un peu tricher , parce que pour mettre en œuvre join(), Perl lui-même doit faire une boucle quelque part à l'intérieur de l'interpètre. Donc, cette réponse peut ne pas satisfaire votre désir de rester sans boucle, selon que vous vouliez éviter une boucle pour des considérations de performance, d'avoir un code plus propre ou plus court.


P.S. Pour obtenir "3" au lieu de "Sony", vous devrez utiliser une boucle - soit de façon évidente, en faisant 1 match dans une boucle en dessous de tout cela; ou en utilisant une bibliothèque qui vous évite d'écrire la boucle vous-même mais qui aura une boucle sous l'appel.

Je vais fournir 3 solutions alternatives.

# 1 option: - mon préféré.Utilise "carte", que je considère personnellement encore une boucle:

my @strings = ("Canon", "HP", "Sony"); 
my $search_in = "Sony's 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 Option: Utilise une boucle cachée derrière une belle méthode de bibliothèque CPAN:

use List::MoreUtils qw(firstidx); 
my @strings = ("Canon", "HP", "Sony"); 
my $search_in = "Sony's 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 Option: Voici le chemin de la boucle évidente:

my $found_index = -1; 
my @strings = ("Canon", "HP", "Sony"); 
my $search_in = "Sony's 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. 
+0

Merci pour cette réponse détaillée et informative: D Cette information est bien écrite et utile. –

+0

J'ai une autre question à ce sujet. Je voudrais mettre en œuvre ceci avec un tableau bidimensionnel de valeurs à rechercher, mais je ne suis pas sûr de savoir comment le faire avec tout sauf l'option 3. Suggestions à ce sujet? (J'ai édité la question pour refléter le nouveau tableau) –

+0

@Ben - vous pouvez le créer comme une nouvelle question ... (lien vers celui-ci), ainsi les gens peuvent bénéficier en termes de recherche. – DVK

1

un moyen facile est juste d'utiliser un hachage et regex:Naturellement, le retour peut tout aussi bien être une impression. Vous pouvez également entourer la chose entière dans une boucle while avec:

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

S'il vous plaît aussi jeter un oeil à des caractéristiques regex de Perl à perlre et jeter un oeil à des structures de données Perl à perlref

EDIT

comme je viens de le faire remarquer que vous essayiez d'éviter d'utiliser une boucle. Une autre méthode consisterait à utiliser la fonction de carte de Perl. Jetez un oeil here.

+0

L'OP a spécifiquement indiqué "ou suis-je condamné à utiliser une boucle?" - ce qui me semble être qu'il sait qu'il peut le faire en boucle et qu'il cherche une réponse sans boucle. Je pourrais le mal lire – DVK

+0

Merci de le souligner, complètement raté. –

+0

Heh ... bien sûr la carte peut être considérée comme une boucle déguisée :) – DVK

2

Voici une solution qui construit une expression régulière avec le code intégré pour incrémenter l'index comme perl se déplace à travers l'expression rationnelle:

my @brands = qw(Canon HP Sony); 
my $string = "Sony's 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's array indexing starts with 0) 

La chaîne qui est accolé à chaque marque de première incrémente l'indice, puis tente pour correspondre à la marque (s'est échappé avec quotemeta (comme \Q) pour permettre des caractères spéciaux regex dans les noms de marque). Lorsque la correspondance échoue, le moteur de regex dépasse l'alternance | et le motif se répète.

Si vous devez comparer plusieurs chaînes, veillez à réinitialiser $index avant chacune. Ou vous pouvez ajouter (?{$index = -1}) à la chaîne regex.

0

Vous pouvez également jeter un oeil à Regexp::Assemble, qui va prendre une collection de sous-expressions rationnelles et construire une seule super-regex qui peut ensuite être utilisé pour tester tous à la fois (et vous donne le texte qui correspondait à la regex, bien sûr). Je ne suis pas sûr que ce soit la meilleure solution si vous regardez seulement trois chaînes/expressions régulières que vous voulez faire correspondre, mais c'est définitivement la solution si vous avez un ensemble de cibles sensiblement plus grand - le projet que j'ai utilisé initialement possède une bibliothèque de quelque 1500 termes avec lesquels il correspond et il fonctionne très bien.