HP-User: Perl-Vergleich für zwei Variabeln gleichzeitig

Abend Forum

Angenommen ich habe eine CSV-Datei mit folgendem Inhalt:
Obstnummer(Zahl einmalig/Identifizierer), Obsthaltbarkeit(Tage), Obstfarbe, Obstname

Also:
1001, 10, grün, Apfel
1002, 5, rot, Kirsche
1003, 12, gelb, banane
usw...

Jetzt ist es mir möglich, nach einem Kriterium zu filtern (Obstname):

zuerst die Zeile einlesen
my $zeile = <LESER>;

Zeile aufsplitten:
my ($Obstnummer, $Obsthaltbarkeit, $Obstfarbe, $Obstname) = split(/,/,$zeile);

Dannach vergleichen:
if($Obstname eq "Banane") {mach was in diesen geschweiften Klammern steht}

-> Alle Bananen werden selektiert.Bis hierher kein Problem.

Was, wenn man aber alles Obst herausfiltern will, daß zwingend "rot" sein muss _und_ genau eine Haltbarkeit von "5" Tagen hat. Also beide $-Variablen ($Obstfarbe und $Obsthaltbarkeit) mussen zutreffen, damit die Anweisung in der Klammer ausgeführt werden. Wie schreibe ich das am besten?

Gruß HP-User

  1. Hallo nochmal
    Mir kam da so ne Idee - ein wenig Tricky, aber was anderes fällt mir nicht ein:

    Code:
    ---------------------------------------------

    Anfang:
    ->einlesen der nächsten Variable
    if($Obstfarbe eq "rot") {goto SchonmalRot;}
    goto Anfang;

    SchonmalRot:
    if($Obsthaltbarkeit eq "5") {goto BeidesOK;}
    goto Anfang;

    BeidesOK:
    Anweisungsblock für Listenspeicherung

    ->Normaler Programmweiterlauf...

    ----------------------------------------------

    Erst mal ein Wert checken, wenn gut zur nächsten "Checkstation springen.
    Kann man das so machen, oder geht es besser?

    Gruß HP-User

    1. Hab was gefunden

      Anfang:
      ->einlesen der nächsten Variable
      if($Obstfarbe eq "rot" and $Obsthaltbarkeit eq "5") {Anweisungsblock für Listenspeicherung}
      goto Anfang;

      ->Normaler Programmweiterlauf...

      Perl unterstützt doppelten Vergleich mittels "and".

      Gruß und Ende HP-User

    2. Hi,

      if($Obstfarbe eq "rot") {goto SchonmalRot;}
      goto Anfang;

      SchonmalRot:
      if($Obsthaltbarkeit eq "5") {goto BeidesOK;}
      goto Anfang;

      Hast Du Programmieren anhand von Basic gelernt?

      Oder woher kommt die Verwendung von goto?

      cu,
      Andreas

      --
      Warum nennt sich Andreas hier MudGuard?
      O o ostern ...
      Fachfragen per Mail sind frech, werden ignoriert. Das Forum existiert.
      1. Hi Andreas

        Hast Du Programmieren anhand von Basic gelernt?

        Oder woher kommt die Verwendung von goto?

        Ja, in der Tat. Goto ist aber eine in Perl verfügbare Anweisung. Und fast alle meine Programme (Perl) -die übrigens funktionieren- nutzen mehr oder weniger die goto-Anweisung.

        Ich weis, daß dies ein ungern gesehener "Befehl" ist, und man den Leuten, die ihn benutzen einen -sagen wir mal- "schlechten" Programierstil nachsagt. Trotz allem gibt es Situationen, da kommt man nicht drumrum - mehr oder weniger ...

        Gruß HP-User

        1. meine Programme (Perl) -die übrigens funktionieren-

          So so. Schreibst du Tests?

          man den Leuten, die ihn benutzen einen -sagen wir mal- "schlechten" Programierstil nachsagt.

          Das ist nicht der Punkt. Programme mit goto (Basic-Variante) erzeugen per se einen nicht mehr im Kopf zu entwirrenden Ausführungspfad, weil es Einstiegspunkte gibt, die sich nicht zurückverfolgen lassen. Dadurch wird der Wartungsprogrammierer um eine Methode beraubt, den Code zu verstehen und zu debuggen. Das ist ein irre hoher Preis für den Nutzen, der dadurch erkauft wird - dass der ursprüngliche Programmierer unbekümmert ein Stück Syntax einsetzen kann, ohne an die Folgen zu denken.

          Ich wette, du warst noch nie in der Situation, den unstrukturierten Spaghetticode von jemand anderem entwirren zu müssen. Wer einmal diese Qual leidet, lernt daraus und schreibt sowas nicht mehr absichtlich.

          Trotz allem gibt es Situationen, da kommt man nicht drumrum

          Das stimmt nicht. Du bist bloß so unerfahren über die vielfältige in Perl verfügbare Syntax für Kontrollstrukturen. Die meiste Zeit, wo du in primitiveren Sprachen zu goto greifen musst, verwendest du in Perl last oder next, eventuell mit Blocklabel.

          (Hinweis an die Experten: ich bin mir bewusst, dass Perl zwei Varianten von goto hat und finde die Fortran-Variante gelegentlich nützlich. Ich will diese Kritik einfach halten.)

  2. auf die Schnelle;

    Du brauchst ordentliche Datenstrukturen, überlege Dir, wie Du die Datei auf einen Hash bekommst. Der Rest ist dann einfach:

      
    use strict;  
    use warnings;  
    use Data::Dumper;  
      
    my $file = {  
    	foo => {  
    		color => 'red',  
    		age => 3,  
    	},  
    	bar => {  
    		color => 'red',  
    		age => 4,  
    	},  
    	baz => {  
    		color => 'black',  
    		age => 99,  
    	},  
    };  
      
    my @red = grep{ $file->{$_}{color} eq 'red'}keys %{$file};  
    my @black = grep{ $file->{$_}{color} eq 'black'}keys %{$file};  
      
    print Dumper \@red, \@black;  
    
    

    Perlhuhn Hotti

    1. Auch auf die Schnelle

      Du brauchst ordentliche Datenstrukturen, überlege Dir, wie Du die Datei auf einen Hash bekommst.

      Ein Hash kann aber meines Wissens nur zwei Elemente speichern: Name und Wert.

      Da müsste ich ja die schöne eingelesene CSV-Zeile, die auch noch so schön gesplittet wurde (split) noch weiter zerlegen -> in eine Zweierkombination?
      Ist das nicht noch viel umständlicher? Sind Arrays da nicht viel angenehmer?
      Mit @Arrayname[Position+1] komm ich doch auch direkt an den Array-Inhalt.

      Gruß HP-User

      1. hi,

        Ein Hash kann aber meines Wissens nur zwei Elemente speichern: Name und Wert.

        Ein hash hat key => value, das ist richtig. Der value kann jedoch wiederum ein key sein, welcher auf einen Speicherbereich zeigt (Referenz):

          
        $hash{oid}{att} = $value;  
        
        

        Keys in hashes sind Scalare. Referenzen sind auch Scalare. Ein Value kann auch eine Array-Referenz sein:

          
        $hash{arr} = [1,2,3];  
        
        

        Hast Du anstelle hash eine hash-Referenz kommt der Arrow-Operator ins Spiel

          
        $href->{oid}->{att} = $val;  
        
        

        Wobei der zweite Pfeil auch weggelassen werden kann, weil an dieser Stelle sowieso eine Referenz erwartet wird:

          
        $href->{oid}{att} = $val;  
        
        

        Da müsste ich ja die schöne eingelesene CSV-Zeile, die auch noch so schön gesplittet wurde (split) noch weiter zerlegen -> in eine Zweierkombination?

        Überlegung: Welches Feld in der CSV ist eindeutig und als Key zu gebrauchen? Wenn keines nicht, nimm die Zeilennummer als Key, steht in $.

        Ist das nicht noch viel umständlicher? Sind Arrays da nicht viel angenehmer?

        Mit @Arrayname[Position+1] komm ich doch auch direkt an den Array-Inhalt.

        Benamste Attribute sind immer besser zu handhaben und besser lesbar als ein stupider numerischer Index [0].

        Hotti

        --
        Wenn der Kommentar nicht zum Code passt, kann auch der Code falsch sein.
        1. Hi hotti

          Mit @Arrayname[Position+1] komm ich doch auch direkt an den Array-Inhalt.

          1. Korrektur: @Arrayname[Position-1] <- Da ja mit Null angefangen wird zu zählen.

          Benamste Attribute sind immer besser zu handhaben und besser lesbar als ein stupider numerischer Index [0].

          2. Ja, das mag zutreffen. Wenn man aber noch nie mit einem Hash zu tun hatte greift man wahrscheinlich lieber zum Array, das man schon kennt ;-)

          3. Ich seh schon, da kennt sich einer aber mächtig gut mit Perl aus! Also wenn's wieder mal klemmt, weis ich, an wenn ich mich wenden muss :-)

          Das mit dem Obst war eh nur ein stupides Beispiel - im aktuellen Projekt geht es um was ganz anderes - ich wollte nur ergründen, ob eine mögliche Mehrfachauswahl des Clienten am Browser machbar ist. Ist schon ne Weile her, das ich mit Perl "herumgemacht" habe.

          So, muss jetzt weg vom PC - zur Webseitenerstellung gehört auch eine ordentliche Handarbeit. Will heissen:

          -Aufbauüberlegung, Skizzen, Zeichnungen, "Rumgemale"
          -Form & Farben
          -Bedienung
          -Bilder, Motive, Grösse
          -Inhalt, Inhalt, Inhalt!!!

          Und ausserhalb des Monitors kommen einem meist frische Ideen.

          In diesem Sinne - Gute Nacht

          Grüsse HP-User

  3. Hi,

    Was, wenn man aber alles Obst herausfiltern will, daß zwingend "rot" sein muss _und_ genau eine Haltbarkeit von "5" Tagen hat.

    Dafür gibt es logische Operatoren.

    perldoc perlop

    liefert die Doku.

    cu,
    Andreas

    --
    Warum nennt sich Andreas hier MudGuard?
    O o ostern ...
    Fachfragen per Mail sind frech, werden ignoriert. Das Forum existiert.
  4. Moin Moin!

    Abend Forum

    Angenommen ich habe eine CSV-Datei mit folgendem Inhalt:
    Obstnummer(Zahl einmalig/Identifizierer), Obsthaltbarkeit(Tage), Obstfarbe, Obstname

    Also:
    1001, 10, grün, Apfel
    1002, 5, rot, Kirsche
    1003, 12, gelb, banane
    usw...

    Jetzt ist es mir möglich, nach einem Kriterium zu filtern (Obstname):

    zuerst die Zeile einlesen
    my $zeile = <LESER>;

    Zeile aufsplitten:
    my ($Obstnummer, $Obsthaltbarkeit, $Obstfarbe, $Obstname) = split(/,/,$zeile);

    "Schon falsch!!" ist übertrieben, aber CSV-Dateien liest man typischerweise mit Text::CSV ein. Schlicht und ergreifend, weil CSV-Dateien einige Gemeinheiten enthalten können, die mit einem schlichten split nicht zu erschlagen sind, angefangen bei Quotes und Trennzeichen in gequoteten Werten.

    Für Deinen trivialen Fall mag split gerade noch ausreichen.

    Ist Dir übrigens bewußt, dass die meisten Felder mit einem Leerzeichen anfangen?

    Dannach vergleichen:
    if($Obstname eq "Banane") {mach was in diesen geschweiften Klammern steht}

    Das wird bei deinem Beispiel nie passieren, denn Du hast nur " banane", was etwas deutlich anderes ist als "Banane".

    -> Alle Bananen werden selektiert.Bis hierher kein Problem.

    Was, wenn man aber alles Obst herausfiltern will, daß zwingend "rot" sein muss _und_ genau eine Haltbarkeit von "5" Tagen hat. Also beide $-Variablen ($Obstfarbe und $Obsthaltbarkeit) mussen zutreffen, damit die Anweisung in der Klammer ausgeführt werden. Wie schreibe ich das am besten?

    Ergänzend zu den reinen Perl-Lösungen:

    Du kannst mit DBI und DBD::CSV CSV-Dateien als Mini-Datenbanken benutzen, und damit den Weg für einen leichten Wechsel auf "richtige" Datenbanken legen:

      
    #!/usr/bin/perl  
      
    use strict;  
    use warnings;  
      
    use DBI;  
    use Data::Dumper;  
      
    my $dbh=DBI->connect(  
    	'dbi:CSV:', # DB-Treiber und DB-Verbindung ("connect string")  
    	'',         # Login (sinnfrei für CSV)  
    	'',         # Passwort (sinnfrei für CSV)  
    	{ # Optionen:  
    		# DBI generisch:  
    		RaiseError => 1,               # Fehler rufen die() auf  
    		AutoCommit => 1,               # Transaktionen explizit starten  
    		FetchHashKeyName => 'NAME_lc', # fetchXXX_hashref liefert Spaltennamen in Kleinbuchstaben  
    		# spezifisch für DBD::CSV:  
    		f_dir => '.',                  # Dateien (=Tabellen) liegen im aktuellen Verzeichnis  
    		f_ext => '.csv',               # Dateien haben die Endung ".csv"  
    		f_encoding => 'utf8',          # Dateien sind UTF8-codiert  
    		csv_eol => "\n",               # Zeilen enden mit "\n"  
    		csv_sep_char => ',',           # Spalten sind mit "," getrennt  
    		csv_quote_char => '"',         # Strings sind mit '"' gequoted  
    		csv_escape_char => '"',        # '"' innerhalb von Strings wird als '""' geschrieben  
    		csv_tables => {  
    			# Einstellungen für bestimmte Tabellen  
    			obst => {  
    				# Einstellung für Tabelle "obst"  
    				file => 'obst.csv',    # Dateiname (redundant)  
    				col_names => [qw( id haltbarkeit farbe name )], # Spaltennamen - stehen normalerweise in der ersten Zeile  
    				skip_first_row => 0,   # Erste Zeile enthält schon Datensätze  
    			},  
    		},  
    	}  
    );  
      
    my $sth=$dbh->prepare(q[  
    	SELECT  
    		id,  
    		name  
    	FROM  
    		obst  
    	WHERE  
    		haltbarkeit = ?  
    		AND  
    		farbe = ?  
    ]);  
    # ^-- Platzhalter benutzen. IMMER. Wenn nicht, sollen Dir die Finger abfaulen.  
    $sth->execute(5,"rot");  
    while (my $hr=$sth->fetchrow_hashref()) {  
    	print Dumper($hr);  
    }  
    $sth->finish();  
    $dbh->disconnect();  
    
    

    Zugegeben, für Dein Mini-Problem ist das Overkill.

    Aber wenn Du irgendwann einmal von CSV-Dateien weg kommst und eine richtige Datenbank benutzt, mußt Du (idealerweise) nur den connect-Aufruf ändern, und das Programm arbeitet ohne weitere Änderungen z.B. mit PostgreSQL:

      
    my $dbh=DBI->connect(  
    	'dbi:Pg:dbname=obstkiste',  
    	'obstler',  
    	'geheim',  
    	{  
    		RaiseError => 1,  
    		AutoCommit => 1,´  
    		FetchHashKeyName => 'NAME_lc',  
    		pg_enable_utf8 => 1,  
    	}  
    );  
    
    

    Oder mit Oracle:

      
    my $dbh=DBI->connect(  
    	'dbi:Oracle:obstkiste',  
    	'obstler',  
    	'geheim',  
    	{  
    		RaiseError => 1,  
    		AutoCommit => 1,´  
    		FetchHashKeyName => 'NAME_lc',  
    	}  
    );  
    
    

    Oder mit SQLite:

      
    my $dbh=DBI->connect(  
    	'dbi:SQLite:dbname=obstkiste.sqlite',  
    	'obstler',  
    	'geheim',  
    	{  
    		RaiseError => 1,  
    		AutoCommit => 1,´  
    		FetchHashKeyName => 'NAME_lc',  
    	}  
    );  
    
    

    Oder mit jeder x-beliebigen Datenbank, die per ODBC ansprechbar ist:

      
    my $dbh=DBI->connect(  
    	'dbi:ODBC:obstkiste',  
    	'obstler',  
    	'geheim',  
    	{  
    		RaiseError => 1,  
    		AutoCommit => 1,´  
    		FetchHashKeyName => 'NAME_lc',  
    	}  
    );  
    
    

    Oder mit jeder x-beliebigen Datenbank, die per JDBC ansprechbar ist:

      
    my $dbh=DBI->connect(  
    	'dbi:JDBC:hostname=obstkiste.example.com;port=9001;url=jdbc:opentext:db:',  
    	'obstler',  
    	'geheim',  
    	{  
    		RaiseError => 1,  
    		AutoCommit => 1,´  
    		FetchHashKeyName => 'NAME_lc',  
    	}  
    );  
    
    

    Und notfalls sogar zur DB-Karrikatur MySQL:

      
    my $dbh=DBI->connect(  
    	'dbi:mysql:database=obstkiste;host=evil.example.com;port=3306',  
    	'obstler',  
    	'geheim',  
    	{  
    		RaiseError => 1,  
    		AutoCommit => 1,´  
    		FetchHashKeyName => 'NAME_lc',  
    	}  
    );  
    
    

    Zu Platzhaltern siehe z.B. http://perlmonks.org/?node_id=811732 und http://perlmonks.org/?node_id=839078.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
  5. Was, wenn man aber alles Obst herausfiltern will, daß zwingend "rot" sein muss _und_ genau eine Haltbarkeit von "5" Tagen hat. Also beide $-Variablen ($Obstfarbe und $Obsthaltbarkeit) mussen zutreffen, damit die Anweisung in der Klammer ausgeführt werden. Wie schreibe ich das am besten?

      
    if ('rot' eq $Obstfarbe && 5 == $Obsthaltbarkeit)  
    
    
    1. Moin Moin!

      Was, wenn man aber alles Obst herausfiltern will, daß zwingend "rot" sein muss _und_ genau eine Haltbarkeit von "5" Tagen hat. Also beide $-Variablen ($Obstfarbe und $Obsthaltbarkeit) mussen zutreffen, damit die Anweisung in der Klammer ausgeführt werden. Wie schreibe ich das am besten?

      if ('rot' eq $Obstfarbe && 5 == $Obsthaltbarkeit)

        
      Hübsch mit Konstanten vorne, funktioniert bei der gezeigten Eingabe (mit reichlich Leerzeichen) und dem gezeigten split (an /,/) nur leider nicht. In $Obstfarbe steht bestenfalls " rot", aber niemals "rot".  
        
      Alexander
      
      -- 
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
      
      1. Hübsch mit Konstanten vorne, funktioniert bei der gezeigten Eingabe (mit reichlich Leerzeichen) und dem gezeigten split (an /,/) nur leider nicht. In $Obstfarbe steht bestenfalls " rot", aber niemals "rot".

        Dann willst Du das vorher "putzen". Außerdem kannst Du nur Antworten auf Fragen bekommen, die Du auch gestellt hast.

        chomp entfernt "Trennzeichen" am Ende einer Zeichenkette. Hier Deinen Zeilenumbruch.

        $dummy = chomp($str);  
        #gibt die Anzahl der entfernten Zeichen zurück und schreibt den geputzen String in die Variable zurück. Wenn Du den Erfolg nicht messen willst:  
        chomp($str);  
          
        #Eventuell willst Du dies vor dem Split tun.  
          
        $str =~ s/^ //;  
        #Ersetzt das führende Leerzeichen und schreibt den "geputzen" String in die Variable zurück.
        
        1. hi,

          chomp entfernt "Trennzeichen" am Ende einer Zeichenkette. Hier Deinen Zeilenumbruch.

          Hmm, eine klitzekleine Anmerkung:

          chomp() entfernt den $INPUT_RECORD_SEPARATOR am Ende einer Zeichenkette, was der $INPUT_RECORD_SEPARATOR beinhaltet, steht in $/ und ist abhängig von der Plattform. Zum Thema 'Zeilenendezeichen' gibt es hier auch einen interessanten Artikel:

          Zeilenumbrüche danke Roland!!!

          Was Perl betrifft: Der Inhalt von $/ kann im Script anders definiert werden, hier also aufpassen und möglichst so:

            
          {  
             local $/ = $wo_du_wolle";  
          }  
          
          

          local sichert die Voreinstellung an einem 'sicheren Ort', das ist der Stack. Wenn also der Block {} beendet ist, gilt wieder die Voreinstellung.

          Der Vollständigkeit halber eine Alternative zu chomp()

            
           $s = unpack 'A*', $s;  
          
          

          Das entfernt alle Leerzeichen am Ende $s UND auch einen etwaigen Zeilenumbruch.

          Nochn Tipp zu split: Nimmt als Expr. $_ und splittet im Default an \s+

          Hotti

  6. Moin,

    Perl ist eine gute Wahl, wenn es um Dateien geht und Jeder fängt wohl mal mit CSV-Dateien an, wenn es darum geht, eigene 'Daten' im FS abzulegen. Etwas besser strukturiert und trotzdem noch gut zu bearbeiten, sind Textdateien in Stil eine ini:

    [section]
    parameter = value
    p = v

    [object-id]
    attribut = value

    [oid]
    att = val

    Am zweiten und dritten Abschnitt siehst Du schon: Das geht in Richtung OOP ;)

    Zum Lesen von iniDateien gibt es ein erstklassiges Modul 'Config::IniFiles', das ist pures Perl und kann entweder ins eigene Libverzeichnis kopiert oder wie üblich installiert werden. Hast Du ActivePerl, tipp mal auf der Kommandozeile ein:

    ppm install Config::IniFiles

    oder Linux &co

    cpan -i Config::IniFiles

    Mit Config::IniFiles gibt es zwei Wege, an die Daten zu kommen, entweder nimmst Du die Methoden über das ini-Object oder bindest Die Datei an einen Hash (tie):

      
    tie my %h, 'Config::IniFiles', -file => '/path/ini';  
    # anm.: -file => $dateihandler ist ebenso möglich  
    # oder so, einfach mal zum Testen einer ini unterhalb __DATA__ im Script:  
    use strict;  
    use warnings;  
    use Config::IniFiles;  
    use Data::Dumper;  
      
    tie my %h, 'Config::IniFiles', -file => *DATA;  
    print Dumper \%h;  
    __DATA__  
    [object-id]  
    attribut = value  
      
    [oid]  
    att = val  
      
    
    

    Data::Dumper ist ein weiteres wichtiges Werkzeug zum optischen Ausgeben von Variablen bzw. Datenstrukturen. Wie schon gesagt, Hashes werden in Perl sehr oft verwendet, vielmehr auch Hash-Referenzen (Objekte).

    Weiter interessante Möglichkeiten ergeben sich aus der abstrakten Trennung von Datenhaltung und Anwendung (Perlscript). D.h., zwischen der Anwendung und der Datenquelle liegt eine austauschbare Klasse, der Data Abstraction Layer (DAL).

    In der Anwendung selbst, wird nur noch mit Datenstrukturen (Hashes) oder Methoden gearbeitet, SQL-Statements oder Operationen mit Dateihandler sind nicht in der Anwendung notiert, sondern im DAL. Auf diese Art und Weise ergibt sich ein übersichtlicher modularer Code...

    Schönen Sonntag,
    Hotti