2016-10-09 4 views
1

Für einen Dateifilter möchte ich ein Array von Wörtern verwenden, wobei Zeilen überprüft werden, wenn sie mit einem der Wörter übereinstimmen.Perl, Suchzeichenfolge für das Auftreten von Elementen des Arrays

ich schon einen recht einfachen Ansatz so aus (nur die wesentliche Anpassungsteil):

# 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; 

viele obskure Syntax und Lösungen in Perl gesehen haben, ich frage mich, ob (oder vielmehr das, was) sind kompaktere Möglichkeiten, dies zu schreiben.

Antwort

3

Erstellen eines regulären Ausdrucks von allen Worten und tun nur ein Spiel:

#!/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; 
} 

/\b(?:$regex)\b/ Passende bones von passenden one verhindern würde.

+0

[ 'list2re' von Data :: Munge] (https://metacpan.org/pod/Data::Munge#list2re-LIST) ist etwas sehr ähnlich, aber Griffe auch mehrere Grenzfälle . – melpomene

+0

+1 von OP. Ich mag dieses hier (es zeigt die aracane Arten von Perl, die ich erwartet hatte). Aber der mit der Montage passt besser zu meinen Bedürfnissen. danke für die Antwort. – Terminality

2

Verwenden Sie Regexp::Assemble, um die Suche in einen Regex umzuwandeln. Auf diese Weise muss jeder String nur einmal gescannt werden, wodurch er für eine größere Anzahl von Zeilen effizienter wird.

Regexp :: Assemble ist es vorzuziehen, es manuell zu tun. Es hat eine vollständige API von Dingen, die Sie mit einer solchen Regex machen könnten, es kann Edge-Cases handhaben und es kann sich intelligent zu einem effizienteren Regex zusammenstellen.

Zum Beispiel produziert dieses Programm (?^:\b(?:t(?:hree|wo)|one)\b), was zu weniger Rückverfolgung führen wird. Dies wird SEHR wichtig, wenn Ihre Wortliste größer wird. Neuere Versionen von Perl, etwa 5.14 und höher, werden dies für Sie tun.

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; 
} 

beachten Sie auch die foreach style loop to iterate over an array, und die Verwendung eines statement modifier.

Schließlich habe ich \b verwendet, um sicherzustellen, dass nur die tatsächlichen Wörter übereinstimmen, nicht Teilstrings wie money.

+0

Moderne Perl-Versionen kompilieren 'one | two | three' intern in einen Trie, was kein Backtracking bewirkt. – melpomene

+0

@melpomene Ja, es ist eine Erwähnung wert. Vielen Dank. Es ist eine sehr nette Optimierung, aber jetzt haben Sie eine Effizienzabhängigkeit von der Perl-Version (ich möchte sagen [diese wurde stabil um 5,14]) (http://perldoc.perl.org/5.14.0/perldelta.html#Regular- Expression-Bug-Fixes)?) Und ich bin mir nicht sicher, wie kompliziert ein Regex damit umgehen kann. Ich würde mich nicht auf etwas verlassen, dessen Leistung entscheidend von der Regex-Optimierung abhängt. Und Regexp :: Assemble löst so viele andere Probleme, dass es sich immer noch lohnt. – Schwern

+0

Ich brauche schon 5.10+ für andere Dinge, also ist das kein großes Problem für mich, aber Punkt. – melpomene

2

Dies ist vielleicht eine allzu simple "Übersetzung" Ihres C-ähnlichen Codes in Perl.

  • Pro: Es ist kompakt
  • Con: Es ist nicht sehr effizient (die anderen Antworten gibt eine Tonne besser hier).
@words= qw/one 
two 
three/; 
$term= "too for the show"; 

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

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

Wenn Sie nur eine Anzahl benötigen, funktioniert auch 'my $ count = grep {$ term = ~/$ _ /} @ words'. – melpomene

+0

+1 von OP. Ich mag dieses hier (es zeigt die aracane Arten von Perl, die ich erwartet hatte). Aber der mit der Montage passt besser zu meinen Bedürfnissen. danke für die Antwort. – Terminality

Verwandte Themen