Ich habe ein fast inhaltsgleiches Posting schon gestern abgesetzt. Aber
irgendwie scheinen die NNTP-Server hier (Uni) zu spinnen. Google hat's
jedenfalls nicht, also nochmal über einen anderen Anbieter. Das gab mir
auch die Gelegenheit ein paar Sachen zu ändern.
Ich möchte eine Datenstruktur bestehend aus Strings, Arrays,
Array- und Hashreferenzen durchlaufen und Werte darin ändern. Also aus
$VAR1 = {
'eins' => 'zwei',
'drei' => 'vier',
'fünf' => [
'sechs', 'sieben', [
'acht',
{ 'neun' => 'zehn' }
]
]
};
soll dann etwa
$VAR1 = {
'eins' => 'ZWEI',
'drei' => 'VIER',
'fünf' => [
'SECHS', 'SIEBEN', [
'ACHT',
{ 'neun' => 'ZEHN' }
]
]
};
werden, wenn das Ziel ist, alle Strings in Großbuchstaben zu wandeln.
Welche Transformation dabei letztlich angewendet wird, soll natürlich
definierbar sein.
Strukturerhaltend soll hierbei heißen, dass sich alle Datentypen an der
gleichen Stelle wie vorher befinden¹ und dass auf Hashes wie vor der
Transformation zugegriffen werden kann (der Schlüssel soll also nicht
verändert werden). Ich mache das bisher mit folgendem rekursiven Code
(lauffähig unter http://paste.debian.net/51563/ verfügbar):
my $a = {
eins => 'zwei',
drei => 'vier',
'fünf' => [
'sechs',
'sieben',
[ 'acht', { neun => 'zehn' } ]
]
};
upupup($a);
sub upupup {
# fall 1, @_ ist ein array
return map {upupup($_)} @_ if scalar(@_) > 1;
# fall 2, @_ ist eine arrayreferenz
return [map {upupup($_)} @{$_[0]}]
if scalar(@_)==1 and ref($_[0]) eq 'ARRAY';
# fall 3, @_ ist eine hashreferenz
if (scalar(@_)==1 and ref($_[0]) eq 'HASH') {
foreach my $key (keys %{$_[0]}) {
$_[0]->{$key} = upupup($_[0]->{$key});
}
return $_[0];
}
# sonst ist @_ ein string
return uc($_[0]);
}
Der Code erfüllt die Aufgabenstellung, ist aber nicht schön. Dazu
einige Fragen:
1. Ich kann das doch nicht alles mit einem einzigen map abfrühstücken,
richtig? D.h. ich muss die Datenstruktur von Hand durchlaufen und
wieder eins zu eins zusammensetzen, ja? Mit anderen Worten, Perls
map verhält sich so, wie man's von map² erwartet?
2. Die if-Statements und das Hin- und Hercasten ist selten dämlich.
Geht das schöner?
3. Fall 3 geht sicherlich mit weniger Syntax. Aber kann man da auch ein
platzsparendes map benutzen? Ich sehe jedenfalls nicht wie.
4. Gibt es ein Modul welches diese Funktionalität (also eine Art
"Tiefenmap", etwa ein etwas abgespecktes foldr³) schon bietet?
Schöne Grüße!
Tobias
¹ die Reihenfolge der Hashes soll natürlich egal sein :-)
² http://en.wikipedia.org/wiki/Map_(higher-order_function)
³ http://en.wikipedia.org/wiki/Fold_(higher-order_function)
Das ist so natürlich mit dem ursprünglichen Code Quark und funktioniert
nur mit der gegebenen Hashreferenz.
Ich wollte die Datenstruktur eigentlich gar nicht direkt mutieren. So
ist's besser (siehe http://paste.debian.net/51566/):
diff --git a/deepmap.pl_old b/deepmap.pl
index 8eff265..ea040cf 100644
--- a/deepmap.pl_old
+++ b/deepmap.pl
@@ -18,7 +18,7 @@ my $a = {
print "Vorher: ".Dumper($a);
-upupup($a);
+my $b = upupup($a);
sub upupup {
# fall 1, @_ ist ein array
@@ -30,15 +30,16 @@ sub upupup {
# fall 3, @_ ist eine hashreferenz
if (scalar(@_)==1 and ref($_[0]) eq 'HASH') {
+ my $newhashref;
foreach my $key (keys %{$_[0]}) {
- $_[0]->{$key} = upupup($_[0]->{$key});
+ $newhashref->{$key} = upupup($_[0]->{$key});
}
- return $_[0];
+ return $newhashref;
}
# sonst ist @_ ein string
return uc($_[0]);
}
-print "Nachher: ".Dumper($a);
+print "Nachher: ".Dumper($b);
On 2009-11-15 11:07, Tobias Nissen <t...@movb.de> wrote:
> Ich m�chte eine Datenstruktur bestehend aus Strings, Arrays,
> Array- und Hashreferenzen durchlaufen und Werte darin �ndern. Also aus
>
> $VAR1 = {
> 'eins' => 'zwei',
> 'drei' => 'vier',
> 'f�nf' => [
> 'sechs', 'sieben', [
> 'acht',
> { 'neun' => 'zehn' }
> ]
> ]
> };
>
> soll dann etwa
>
> $VAR1 = {
> 'eins' => 'ZWEI',
> 'drei' => 'VIER',
> 'f�nf' => [
> 'SECHS', 'SIEBEN', [
> 'ACHT',
> { 'neun' => 'ZEHN' }
> ]
> ]
> };
>
> werden, wenn das Ziel ist, alle Strings in Gro�buchstaben zu wandeln.
> Welche Transformation dabei letztlich angewendet wird, soll nat�rlich
> definierbar sein.
>
> Strukturerhaltend soll hierbei hei�en, dass sich alle Datentypen an der
> gleichen Stelle wie vorher befinden� und dass auf Hashes wie vor der
> Transformation zugegriffen werden kann (der Schl�ssel soll also nicht
> ver�ndert werden). Ich mache das bisher mit folgendem rekursiven Code
> (lauff�hig unter http://paste.debian.net/51563/ verf�gbar):
[...]
> sub upupup {
> # fall 1, @_ ist ein array
> return map {upupup($_)} @_ if scalar(@_) > 1;
>
> # fall 2, @_ ist eine arrayreferenz
> return [map {upupup($_)} @{$_[0]}]
> if scalar(@_)==1 and ref($_[0]) eq 'ARRAY';
>
> # fall 3, @_ ist eine hashreferenz
> if (scalar(@_)==1 and ref($_[0]) eq 'HASH') {
> foreach my $key (keys %{$_[0]}) {
> $_[0]->{$key} = upupup($_[0]->{$key});
> }
> return $_[0];
> }
>
> # sonst ist @_ ein string
> return uc($_[0]);
> }
>
> Der Code erf�llt die Aufgabenstellung, ist aber nicht sch�n. Dazu
> einige Fragen:
>
> 1. Ich kann das doch nicht alles mit einem einzigen map abfr�hst�cken,
> richtig? D.h. ich muss die Datenstruktur von Hand durchlaufen und
> wieder eins zu eins zusammensetzen, ja? Mit anderen Worten, Perls
> map verh�lt sich so, wie man's von map� erwartet?
Ja.
> 2. Die if-Statements und das Hin- und Hercasten ist selten d�mlich.
> Geht das sch�ner?
1) Nach
return ... if scalar(@_) > 1;
kann @_ nur mehr 0 oder 1 Elemente haben. Wenn Du den Fall 0 Elemente
gleich abpr�fst, werden die Ifs schon einfacher.
2) Wo wird da gecastet? (Was ist �berhaupt ein cast in Perl?) Meinst Du
scalar? Oder die Dereferenzierungen @{...} und %{...}?
3) Variablen helfen das ganze �bersichtlicher zu machen.
Damit komme ich mal auf:
sub upupup {
# fall 1, 0 oder mindestens 2 Argumente
# in dem Fall rufen wir es einfach f�r jedes Argument auf:
return map {upupup($_)} @_ if scalar(@_) != 1;
# Wir haben genau ein Element:
my $e = $_[0];
# fall 2, @_ ist eine arrayreferenz
return [map {upupup($_)} @$e]
if ref($e) eq 'ARRAY';
# fall 3, @_ ist eine hashreferenz
if (ref($e) eq 'HASH') {
my $new;
foreach my $key (keys %$e) {
$new->{$key} = upupup($e->{$key});
}
return $new;
}
# sonst ist @_ ein string
return uc($e);
}
> 3. Fall 3 geht sicherlich mit weniger Syntax. Aber kann man da auch ein
> platzsparendes map benutzen? Ich sehe jedenfalls nicht wie.
$new = { map { $_ => upupup($e->{$_} } keys %$e };
Damit braucht man das $new nat�rlich nicht mehr und das wird zu:
sub upupup {
# fall 1, 0 oder mindestens 2 Argumente
# in dem Fall rufen wir es einfach f�r jedes Argument auf:
return map { upupup($_) } @_ if scalar(@_) != 1;
# Wir haben genau ein Element:
my $e = $_[0];
# fall 2, @_ ist eine arrayreferenz
return [ map { upupup($_) } @$e ] if ref($e) eq 'ARRAY';
# fall 3, @_ ist eine hashreferenz
return { map { $_ => upupup($e->{$_} } keys %$e } if (ref($e) eq 'HASH');
# sonst ist @_ ein string
return uc($e);
}
Fall 2 kann man noch durch
return [ upupup(@$e) ] if ref($e) eq 'ARRAY';
ersetzen, da genau dieses map ja in Fall 1 auch abgehandelt wird, aber
ich bin mir nicht sicher, ob das eleganter ist (eher w�rde ich
vorschreiben, dass upupup mit genau einem Parameter aufgerufen werden
muss).
> 4. Gibt es ein Modul welches diese Funktionalit�t (also eine Art
> "Tiefenmap", etwa ein etwas abgespecktes foldr�) schon bietet?
fold ist reduce (in List::Util). Aber ich glaube eher nicht, dass das
dadurch eleganter wird.
hp (der den Code jetzt leichtsinnigerweise ungetestet postet)
Du hast natürlich Recht, casts sind das nicht.
[...]
>> 3. Fall 3 geht sicherlich mit weniger Syntax. Aber kann man da auch
>> ein platzsparendes map benutzen? Ich sehe jedenfalls nicht wie.
>
> $new = { map { $_ => upupup($e->{$_} } keys %$e };
Ja, der Wald und die Bäume... Danke!
> Damit braucht man das $new natürlich nicht mehr und das wird zu:
>
> sub upupup {
> # fall 1, 0 oder mindestens 2 Argumente
> # in dem Fall rufen wir es einfach für jedes Argument auf:
> return map { upupup($_) } @_ if scalar(@_) != 1;
>
> # Wir haben genau ein Element:
> my $e = $_[0];
>
> # fall 2, @_ ist eine arrayreferenz
> return [ map { upupup($_) } @$e ] if ref($e) eq 'ARRAY';
>
> # fall 3, @_ ist eine hashreferenz
> return { map { $_ => upupup($e->{$_} } keys %$e } if (ref($e) eq 'HASH');
>
> # sonst ist @_ ein string
> return uc($e);
> }
Super, vielen Dank. Das sieht doch schon viel freundlicher aus.
> Fall 2 kann man noch durch
>
> return [ upupup(@$e) ] if ref($e)
> eq 'ARRAY';
>
> ersetzen, da genau dieses map ja in Fall 1 auch abgehandelt wird, aber
> ich bin mir nicht sicher, ob das eleganter ist (eher würde ich
> vorschreiben, dass upupup mit genau einem Parameter aufgerufen werden
> muss).
Ich beziehe mich auf den Inhalt der Klammer: Du meinst man schränkt sie
auf Referenzen auf Arrays oder Hashes ein? Dann kommt man sicher nicht
um eine zweite Funktion herum. _Aber_: Kann's sein, dass ich es auf
Referenzen einschränken muss, wenn ich es idiotensicher haben möchte?
Wenn da nun jemand ein Hash (keine Hashreferenz) reintut, dann werden
die Schlüssel ja verändert. Und dabei fällt mir gerade auf, dass ich
gar nicht weiß, wie ich herausfinde, ob mir da jemand ein Hash und kein
Array gegeben hat. Geht das überhaupt?
>> 4. Gibt es ein Modul welches diese Funktionalität (also eine Art
>> "Tiefenmap", etwa ein etwas abgespecktes foldr³) schon bietet?
>
> fold ist reduce (in List::Util). Aber ich glaube eher nicht, dass das
> dadurch eleganter wird.
Ne, stimmt, man muss ja doch jeden Fall abhandeln. In den Sprachen in
denen man am meisten fold()et hat man ja meist nur Elemente und Listen
von Elementen, da beschränkt's sich ja meist auf höchstens zwei Fälle.
> hp (der den Code jetzt leichtsinnigerweise ungetestet postet)
Ach, der geht so durch... Vielen dank nochmal!
kannst Du Deine gesamte Struktur in Gro�buchstaben umwandeln. Sollen die
Keys so bleiben, wie sie sind, baust Du Dir eine kleine Funktion, die
das uc ersetzt und tust sie in das eval. Diese Zeile sollte es dann tun:
$VAR2=eval(join("", map{sub{s/(.*=>)*(.*)/$1\U$2/; $_}->($_)}
split(/\r/, Dumper($VAR1))));
J�rg
Jein.
Es geht nicht, wenn du tatsächlich die Parameter "normal" übergibst und
auch anonyme Werte übergeben möchtest. Ansonsten kannst du die Übergabe
als Referenz aber mit Hilfe von Prototypen erzwingen, ohne dass der
Benutzer deiner Funktion extra den \ schreiben müsste - da haben wir
doch tatsächlich mal einen nützlichen Zweck von den Prototypen gefunden!
Das geht so:
sub upupup(\[$@%]) {
print "Ich wurde aufgerufen mit: ", ref shift, "\n";
}
my %h = (42 => 23);
my @a = (2,3,5,7,11);
my $s = "simpel";
upupup(@a);
upupup(%h);
upupup($s);
Was dann aber nicht mehr geht, ist ein Aufruf mit einem Wert, also
upupup("hallo") - aber das ergibt in deinem Fall ja sowieso keinen Sinn.
- Wolf
Danke, gedacht habe ich mir gestern schon sowas, fand's dann aber etwas
zu friemelig. So wie Du es aufschreibst sieht es ja aber gar nicht mehr
so wild aus. Meine erste Reaktion war allerdings: Schön ist das nicht.
Als eingefleischter Perler mag man das "normal" finden. Aber warum darf
ref() mehr wissen als wir, warum gibt es kein direktes is_a_hash()?
Ist das eine der zahlreichen Inkonsistenzen von Perl 5 oder liegt's an
mir? Die meisten Programmiersprachen kennen nur Listen und einzelne
Elemente als elementare Datenstrukturen. Aber da weiß man immer, woran
man ist. Ein Hash /ist/ in Perl aber eine elementare Datenstruktur.
Diese basiert zwar auf Listen mit einer geraden Zahl von Elementen,
aber das interessiert den Programmierer ja in der Regel nicht. Ich habe
so das Gefühl, Skalare und Arrays sind first-class-citizens, Hashes
aber nur second-class. Das stört beim Programmieren recht wenig, wenn
man es erstmal verstanden hat. Aber ganz sauber finde ich das nicht.
Wie sehen die alten Hasen das?
Macht Perl 6 das anders?
Jein. Es gibt keinen Hash-Kontext in Perl 6, aber ansonsten ist der Hash
genauso fundamental wie das Array in Perl 6. Du kannst auch solche
Spässe machen:
sub foo(@array, %hash, $scalar) {
# hier mit %hash etc. arbeiten
}
Ein Hash im List-Kontext gibt in Perl 6 nicht mehr eine Liste key und
value abwechselnd zurück (kann man aber mit %hash.kv immer noch haben,
wenn man will), sondern eine Liste von Pair-Objekten, das erleichtert
den Umgang in einigen Fällen schon ziemlich.
Grüße,
Moritz
--
Moritz Lenz
http://perl-6.de/ http://moritz.faui2k3.org/
ref wei� gar nicht mehr als wir. Der Prototyp sorgt nur daf�r, eine
Referenz auf das (einzige) Argument �bergeben wird. Man schreibt also
upupup(%h);
und der Compiler macht daraus
upupup(\%h);
Innerhalb der Funktion ist dann $_[0] einfach eine Referenz auf %h,
genauso als ob man upupup(\%h) ohne Prototyp aufgerufen h�tte. Ist nur
syntaktischer Zucker, inhaltlich �ndert sich nichts.
Du kannst kein Hash an eine Funktion �bergeben, weil Ceine Funktion immer
eine Liste von Skalaren als Argumente erwartet. Wenn Du sowas schreibst (ohne
Prototyp):
upupup(%h);
dann wird %h im Listenkontext ausgewertet, d.h., Du bekommst eine Liste
die aus den keys und den zugeh�rigen Werten besteht.
Im Prinzip passiert auch bei Arrays das gleiche, nur dass dass eine
Liste der Elemente eines Arrays nicht so leicht vom Array selbst zu
unterscheiden ist. Aber man merkt es z.B., wenn man zwei Arrays an eine
Funktion �bergibt:
upupup(@a, @b);
@_ in upupup enh�lt dann die Konkatenation der Elemente von @a und @b.
Wo @a aufh�rt und @b beginnt, kann nicht mehr festgestellt werden.
> Ist das eine der zahlreichen Inkonsistenzen von Perl 5 oder liegt's an
> mir?
Inkonsistent ist es nicht. Etwas beschr�nkt ist es halt.
hp
> Ich m�chte eine Datenstruktur bestehend aus Strings, Arrays,
> Array- und Hashreferenzen durchlaufen und Werte darin �ndern. Also aus
>
> $VAR1 = {
> 'eins' => 'zwei',
> 'drei' => 'vier',
> 'f�nf' => [
> 'sechs', 'sieben', [
> 'acht',
> { 'neun' => 'zehn' }
> ]
> ]
> };
>
> soll dann etwa
>
> $VAR1 = {
> 'eins' => 'ZWEI',
> 'drei' => 'VIER',
> 'f�nf' => [
> 'SECHS', 'SIEBEN', [
> 'ACHT',
> { 'neun' => 'ZEHN' }
> ]
> ]
> };
>
> werden, wenn das Ziel ist, alle Strings in Gro�buchstaben zu wandeln.
> Welche Transformation dabei letztlich angewendet wird, soll nat�rlich
> definierbar sein.
>
use strict;
use warnings;
package MyVisitor;
use Moose;
extends 'Data::Visitor';
sub visit_value{
$_[1] = uc $_[1] ;
}
package main;
use Data::Dumper;
my $VAR1 = {
'eins' => 'zwei',
'drei' => 'vier',
'f�nf' => [
'sechs', 'sieben', [
'acht',
{ 'neun' => 'zehn' }
]
]
};
my $v = MyVisitor->new;
$v->visit($VAR1);
print Dumper $VAR1;
Gr��e, Christoph
Nice, danke! :-) Hätte mich eigentlich auch gewundert, wenn's da nicht
schon was von Ratio^W^W im CPAN gegeben hätte.