2008-09-01 9 views
25

Ich "brauchte" kürzlich eine Zip-Funktion in Perl 5 (während ich über How do I calculate relative time? nachdachte), d. H. Eine Funktion, die zwei Listen nimmt und sie zu einer Liste "zusammenzieht", die Elemente verschachtelt.Gibt es eine elegante Zip, um zwei Listen in Perl 5 zu verschachteln?

(Pseudo) Beispiel:

@a=(1, 2, 3); 
@b=('apple', 'orange', 'grape'); 
zip @a, @b; # (1, 'apple', 2, 'orange', 3, 'grape'); 

Haskell has zip in the Prelude und Perl 6 has a zip operator in gebaut, aber wie wollen Sie tun es auf elegante Weise in Perl 5?

+0

Haskells Zip ist nicht das, was Sie suchen: Es gibt eine Liste der entsprechenden Paare, keine Liste der verschachtelten Elemente. –

+0

Sie haben Recht; Haskell-Listen enthalten Elemente eines einzelnen Typs. Ich habe nicht nachgedacht, als ich mich hier auf Haskell bezog. – asjo

+2

Oft, wenn man denkt, dass sie eine Zip wollen, ist es ein Hash aus zwei Listen zu erstellen. In diesem Fall ist es besser, ein Hash-Slice zu verwenden. '@hash {@keys} = @ Werte'. Wenn das hier nicht der Fall ist, tut mir leid für den Lärm. –

Antwort

36

Angenommen, Sie haben genau zwei Listen und sie sind genau die gleiche Länge haben, hier ist eine Lösung, die ursprünglich von merlyn (Randal Schwartz), der es pervers Perlish genannt:

sub zip2 { 
    my $p = @_/2; 
    return @_[ map { $_, $_ + $p } 0 .. $p - 1 ]; 
} 

Was hier passiert, ist für ein, dass 10-Elemente-Liste, zuerst finden wir den Drehpunkt in der Mitte, in diesem Fall 5, und speichern Sie es in $p. Dann erstellen wir eine Liste von Indizes bis zu diesem Punkt, in diesem Fall 0 1 2 3 4. Als nächstes verwenden wir map, um jeden Index mit einem anderen Index zu paaren, der sich in der gleichen Entfernung vom Drehpunkt befindet, wie der erste Index von Anfang an ist. Geben Sie uns (in diesem Fall) 0 5 1 6 2 7 3 8 4 9. Dann nehmen wir ein Stück von @_ mit diesem als die Liste der Indizes. Das heißt, wenn 'a', 'b', 'c', 1, 2, 3 an zip2 übergeben wird, wird diese Liste in 'a', 1, 'b', 2, 'c', 3 neu angeordnet.

Dieses in einem einzigen Ausdruck kann wie so entlang ysth die Linien geschrieben werden:

sub zip2 { @_[map { $_, $_ + @_/2 } 0..(@_/2 - 1)] } 

Ob Sie würden wollen entweder Variation hängt verwenden, ob Sie die Erinnerung sehen selbst, wie sie arbeiten, aber für mich, es war ein Gedankenexpander.

+0

wow, das ist klar und prägnant !!! –

+0

+++ 1 smart & short – Viet

+0

mein Geist ist durchgebrannt! – Richard

27

Das List::MoreUtils Modul verfügt über eine zip/Mesh-Funktion, die den Trick tun sollten:

use List::MoreUtils qw(zip); 

my @numbers = (1, 2, 3); 
my @fruit = ('apple', 'orange', 'grape'); 

my @zipped = zip @numbers, @fruit; 

Hier ist die Quelle der Gitterfunktion:

sub mesh (\@\@;\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@) { 
    my $max = -1; 
    $max < $#$_ && ($max = $#$_) for @_; 

    map { my $ix = $_; map $_->[$ix], @_; } 0..$max; 
} 
+0

Ich weiß nicht, wie ich dieses Modul übersehen habe - danke! – asjo

+2

Was sind diese entflohenen At-Zeichen? – dreeves

+1

Prototypen, sagen, es dauert zwei bis 32 Array-Parameter und das Sub wird sie implizit als Arrayrefs erhalten. – ysth

1
 
my @l1 = qw/1 2 3/; 
my @l2 = qw/7 8 9/; 
my @out; 
push @out, shift @l1, shift @l2 while (@l1 || @l2); 

Wenn die Listen a sind andere Länge, dies wird "undef" in die zusätzlichen Slots setzen, aber Sie können dies leicht beheben, wenn Sie dies nicht möchten. Etwas wie (@ l1 [0] & & shift @ l1) würde es tun.

Hoffe, das hilft!

+1

Schöne Lösung, hätte ich wahrscheinlich meine Präferenz für die Änderung der zwei Input-Listen ausgedrückt haben :-) – asjo

2

Algorithm::Loops ist wirklich nett, wenn Sie viel von dieser Art von Sache tun.

Mein eigener Code:

sub zip { @_[map $_&1 ? $_>>1 : ($_>>1)+($#_>>1), [email protected]_] } 
+0

Die Verwendung von Bit-Verschiebungen ist in C zwar schneller, ist aber in Perl nur eine unnötige Verschleierung. Besser geschrieben wie folgt: @_ [map {$ _, $ _ + @ _/2} 0 .. (@ _/2 - 1)] Kürzer auch. –

+1

Es ist kein Problem für die Frage hier, aber meine Zip wurde entworfen, um auch für ungerade Anzahl von Elementen zu arbeiten. – ysth

0

Das ist absolut nicht eine elegante Lösung, noch ist es die beste Lösung, von einer Strecke der Phantasie. Aber es macht Spaß!

package zip; 

sub TIEARRAY { 
    my ($class, @self) = @_; 
    bless \@self, $class; 
} 

sub FETCH { 
    my ($self, $index) = @_; 
    $self->[$index % @$self][$index/@$self]; 
} 

sub STORE { 
    my ($self, $index, $value) = @_; 
    $self->[$index % @$self][$index/@$self] = $value; 
} 

sub FETCHSIZE { 
    my ($self) = @_; 
    my $size = 0; 
    @$_ > $size and $size = @$_ for @$self; 
    $size * @$self; 
} 

sub CLEAR { 
    my ($self) = @_; 
    @$_ =() for @$self; 
} 

package main; 

my @a = qw(a b c d e f g); 
my @b = 1 .. 7; 

tie my @c, zip => \@a, \@b; 

print "@c\n"; # ==> a 1 b 2 c 3 d 4 e 5 f 6 g 7 

Wie STORESIZE/PUSH/POP/SHIFT/UNSHIFT/SPLICE zu handhaben ist eine Übung dem Leser überlassen.

10

Für Arrays mit der gleichen Länge:

my @zipped = (@a, @b)[ map { $_, $_ + @a } (0 .. $#a) ]; 
+0

Wirklich nette Lösung. Es dauert lange, es für mich zu verstehen. –

+3

Dies hat Probleme bei Arrays ungleicher Größe. –

12

finde ich die folgende Lösung einfach und leicht zu lesen:

@a = (1, 2, 3); 
@b = ('apple', 'orange', 'grape'); 
@zipped = map {($a[$_], $b[$_])} (0 .. $#a); 

Ich glaube, es ist auch schneller als Lösungen, die das Array in einem falschen erstellen Bestellen Sie zuerst und verwenden Sie dann Slice, um neu zu ordnen, oder Lösungen, die @a und @b ändern.

+1

Dies hat Probleme bei Arrays ungleicher Größe. –

+1

@briandfoy Ihre Kommentare helfen, aber Sie wissen, was Sie vergessen haben zu tun? weisen Sie darauf hin, welches * für * ungleiche Arrays funktioniert. (Ich kaufe für einen, der das tun muss) –

+0

Ich habe nicht vergessen. Dieses Problem hängt davon ab, was Sie mit den verbleibenden Elementen tun möchten. Sie sollten eine andere Frage stellen und Ihre Einschränkungen angeben. –