linux-l: Perl: wie geht es schoener

Jens Dreger jens.dreger at physik.fu-berlin.de
Sa Apr 22 13:52:02 CEST 2000


On Fri, Apr 21, 2000 at 08:54:14PM +0200, Holger Paulsen wrote:
> Ich habe begonnen, mich ein wenig mit Perl zu beschäftigen;

Lobings !

> selbstverständlich stolpere ich gleich über ein Problem.

voellig klar...
 
> Ich möchte abfragen, ob eine Textzeile
> a) kein leerer String ist
> b) anderes als Whitespace enthält und
> c) kürzer als 20 Zeichen ist.
> 
> Funktionieren tut offensichtlich

...nicht:

> if (($Zeile ne /\s+/) and ($Zeile ne "") and (length($Zeile) < 20)) { ...

> Wie geht das kürzer? Ich stelle es mir als ungünstig auf die
> Performance vor - die allerdings nicht erheblich ist - daß
> eine Variable dreimal geöffnet und ausgewertet wird.

Um die Performance wuerde ich mir in diesem Zusammenhang keine Sorgen
machen. Wenn Du dreimal hintereinander auf denselben String
zugreifst, wird der nicht dreimal wie eine Datei geoeffnet und wieder
geschlossen. "Variable oeffnen" ist sowieso eine etwas seltsame
Formulierung ;-)

Schlimmer ist, dass Deine Zeile _nicht_ funktioniert. Ausserdem ist
eine String, wenn er "anderes als Whitespace enthält" automatisch
"kein leerer String", sprich: aus b) folgt a).

Die ersten beiden Punkte wuerde man wohl daher mit einem

  $Zeile =~ /\S+/ # wir wollen mindestens einen non-whitespace character

abhandeln. Fuer Punkt c) ist "length($Zeile) < 20" vollkommen in
Ordnung, denke ich. Wenn Du gar keine Whitespaces zulassen wuerdest,
koennte man beides zu einem eleganten /^\S{1,20}$/
zusammenfassen. Aber ich habe Dich so verstanden, dass Whitespace
durchaus drin sein darf, sofern auch Non-Whitespace vorkommt.

Ich habe mal folgende "Pattern-Match-Testplatform" zusammengestrickt:

-------------8<--------------------
#!/usr/bin/perl -w

@OPS = ( 
	'$_ ne /\s+/',
	'$_ ne ""',
	'length($_)<20',
	'($_ ne /\s+/) and ($_ ne "") and (length($_)<20)',
	'/\S+/',
	'/\S+/ and length($_)<20',
       );

while(<>) {
  chomp;
  foreach $op (@OPS) { print ((eval $op)+0); }
  s/\s/_/g;
  print " $_\n";
}
------------------8<--------------------

Bei OPS kannst Du einfach nach Herzenzlust Operatoren einfuegen. Die
Ergebnisse werden dann in einer kompakten "0-1-Matrix" angezeigt
(s.u.). Mit dem Input:

-------------8<--------------

                       
        
diese Zeile ist laenger als 20 Zeichen !
diese nicht ;-)
---------------8<---------------

erhaelt man:

dreger at smart:~> ./zeile.pl zeile.dat 
001000 
110000 _______________________
111100 ________
110010 diese_Zeile_ist_laenger_als_20_Zeichen_!
111111 diese_nicht_;-)

Deine Version entspricht der vierten Spalte in dem 0-1er Block,
d.h. es kommen Zeile 3 und 5 durch. Der Grund dafuer ist, dass
das Konstrukt

    ($Zeile ne /\s+/)

nur sehr wenig Sinn macht. Es haette eher

    ($Zeile =~ /\s+/)

heissen muessen. Anderenfalls wird /\s+/ auf $_ angewandt und das
Ergebnis (0 oder 1) mit $Zeile verglichen. Wie man sieht (Spalte 1), 
kann es sogar mal vorkommen, dass $_ gleich /\s+/ ist, naemlich genau
dann, wenn $_ leer ist. Dann ergibt /\s+/ naemlich 0 (kein Match) und
$_ ist auch 0 (leerer String). Das war aber sicherlich nicht gemeint.

Das Skript wandelt Leerzeichen in '_' um, damit man sie bei der
Ausgabe erkennt. Die letzte Spalte entspricht dabei

	/\S+/ and length($_)<20

und liefert, soweit ich das erkennen kann, das richtig Ergebnis. Nur
Zeile "diese nicht ;-)" kommt durch.

Wenn Du Dir Sorgen um die Performance machst, kannst Du um so ein
Skript ja ein while($count--) wrappen, und mit time die Zeit
stoppen. Was man grundsaetzlich vermeiden sollte, ist, pattern zu
definieren, die in fast allen Faellen _nicht_ matchen. Das kann dazu
fuehren, dass die Pattern-Matching-Engine den pattern an allen
moeglichen Positionen ausprobieren muss. Hier kann man Perl wiederum
dadurch unterstuezten, dass man Pattern mit ^ und $ am Anfang und Ende
des Strings verankert.

Gruss,

Jens.



Mehr Informationen über die Mailingliste linux-l