Empirius Tech Blog


map und grep - Abfertigung ohne (sichtbare) Schleife

Obwohl äußerst nützlich, fristen diese beiden Funktionen ein Nischendasein bei vielen Programmierern. Wie und bei welchen Gelegenheiten sie praktisch angewandt werden können, soll hier mal beleuchtet werden.


Gelegenheit macht Schleife

 

Oftmals stehen beim täglichen Coding einfache Aufgaben an, welche über eine Reihe von Daten ausgeführt werden sollen. Ob nun für eine Dateiliste ein Pfad davorgestellt  oder eine Liste von Zahlen z.B. quadriert werden soll - der Ottonormalprogrammierer greift zur Schleife, iteriert über die Liste und führt für jedes Element die gewünschte Operation aus.

 

Alternative map

 

Das selbe Ergebnis kann man auch mit einem map erreichen: Diese Funktion erwartet ein Code-Block und eine Liste:

 

map { codeblock } @eingabeliste;

 

Im Codeblock steht, was passieren soll, in der Liste, womit es passieren soll. Als Ergebnis fällt eine neue Liste - die mit den Einzelergebnissen - ab.

 

Folgende Beispiele sind also äquivalent:

 

my @eingabe = ( 1, 3, 5, 20, 37 );
my @ergebnis;

foreach my $element (@eingabe) {

    push(@ergebnis, $element * $element);

}

 

---

 

my @eingabe = ( 1, 3, 5, 20, 37 );

my @ergebnis = map { $_ * $_ } @eingabe;

 

Dabei gilt es zu beachten, daß das jeweilig aktuelle Element der Liste in der Standardvaiablen $_ im Codeblock verfügbar ist. Eine explizite Benennungsmöglichkeit wie in der foreach-Schleife gibt es nicht. Desweiteren wird für jedes Element der Eingabeliste vom Codeblock eine Ausgabeliste erzeugt. Das bedeutet, daß man neben der Rückgabe eines einzelnen Elements (also einer Liste mit einem Element) auch entweder mehrere Elemente oder kein Element (leere Liste, in Perl mit " () " erzeugt) an die Ergebnisliste anfügen kann.

 

# Ergebnisliste enthält alle ungeraden Zahlen und Ihre Quadrate

@result = map

  {                             # Codeblock

   ($_ % 2)                     # Modulo-Rest als Bedingung

     ? ($_,$_**2)               # wahr-Zweig, Liste mit 2 Elementen

     : ()                       # falsch-Zweig, leere Liste

  }

  qw ( 1 3 6 20 37)             #Eingabeliste

;

 

Der triäre Operator <bedingung> ? <wahr> : <falsch> wird hier verwendet, um auch für den 'falsch'-Zweig ein explizites Ergebnis zurückzuliefern. Wenn man im Codeblock etwa

if ($_ % 2) { return ($_, $_**2) }

verwenden,  würde als Ergebnis für den nicht aufgeführten 'else'-Zweig 'undef' zurückgeliefert - und auch folgerichtig als Element in die Ergebnisliste eingefügt werden. Möchte man also kein Ausgabeelement für nichtzutreffende Bedingungen, einfach die leere Liste  " () " zurückliefern.

 

Damit kommen wir der Arbeitsweise der zweiten vorzustellenden Funktion heute nahe.

 

Isses, oder isses nicht? - grep

 

Ein weiterer in der Praxis häufig auftretender Anwendungsfall ist die Auswahl bestimmter Elemente einer Liste.

Hier kommt die Glanzstunde von grep. Genau wie map erwartet diese Funktion eine Codeblock und eine Eingabeliste. Hier allerdings werden genau die Eingabeelemente in die Ergebnisliste aufgenommen, für die der Codeblock ein boolean'sches TRUE erzeugt.

 

Folgende Beispiele sind also äquivalent:

 

my @eingabe = ( 1, 3, 6, 20, 37 );

my @ergebnis;

foreach my $element (@eingabe) {

  if ($element % 2 == 1) {
    push(@ergebnis, $element);

  }
}

 

---

 

my @eingabe = ( 1, 3, 6, 20, 37 );

my @ergebnis = grep { $_ % 2 == 1 } @eingabe;

 

Beide Codestücken liefern also ungerade Zahlen. Grep eignet sich allerdings noch viel besser, wenn es einen lediglich interessiert, ob Elemente in einer Liste vorkommen, die eine bestimmte Bedingung erfüllen. Da eine Liste im skalaren Kontext die Anzahl der enthaltenen Elemente liefert, wäre uns in diesem Beispiel schon mit

 

printf "%d ungerade Elemente\n", grep { $_ %2 == 1} @eingabe;

 

geholfen. Eine foreach-Schleife hätte wohl mehr Code gebraucht.

 

Auch in Kombination sind die beiden anzutreffen: da jede Funktion eine Liste als Eingabe braucht UND auch eine Liste ausgibt, kann man sie leicht verketten:

 

#gibt alle Elemente in Großschreibung aus, welche a oder o enthalten

print map { uc } grep { /a|o/ } qw( ba be bi bo bu);

 

Hier verwendet der map-Befehl die Liste, welche als Ergebnis des grep-Befehls herauskommt - man sollte also von rechts nach links 'lesen'.

 

Fazit

 

Gerade für kleine Aufgaben ist ein map bzw. grep oft die kürzere Alternative. Und wenn man weiß, wie die beiden Funktionen arbeiten, oft auch die lesbarere.

 

 


Tech Blog <- Zurück


Kommentare (1)

  1. Empirius:
    Oct 07, 2014 at 01:12 AM

    Als alter Unix-Skript-ler ist mir grep natürlich von der Shell bekannt und damit auch in perl gern verwendet, anders als das - für mich - unbekannte Mysterium "map" ...

    Das, was mir im perl noch fehlt, das grep auf Dateien. Das gibt es zwar als CPAN Modul (File::Grep, Fkt. fgrep), aber es macht irgendwie nicht 100% das, was man vom Unix-Kommando her kennt ...:
    Im Original liefert das perl-fgrep einen strukturierten Hash mit count, filename und matches. Erwarten würde ich nur ein einfaches Array mit allen Treffern, so wie es eben auch map und grep tun.

    Daher habe ich das File::Grep ein bisschen ge-tweaked. Bei Interesse ... gerne.

    Ein evtl. Nachteil sei erwähnt: Getrenntes Handling bei mehreren Dateien dann nicht möglich (z.B. *.log).


Kommentar hinzufügen:





Erlaubte Tags: <b><i><br>Kommentar hinzufügen: