2008-09-01 6 views
25

J'ai récemment "besoin" d'une fonction zip en Perl 5 (alors que je pensais à How do I calculate relative time?), c'est-à-dire une fonction qui prend deux listes et les "zippe" ensemble en une liste, entrelaçant les éléments.Existe-t-il un élégant zip pour entrelacer deux listes dans Perl 5?

(pseudo) Exemple:

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

Haskell has zip in the Prelude et Perl 6 has a zip operator construit, mais comment faites-vous d'une manière élégante en Perl 5?

+0

Le zip de Haskell n'est pas ce que vous cherchez: il renvoie une liste de paires correspondantes, pas une liste d'éléments entrelacés. –

+0

Vous avez raison; Les listes Haskell contiennent des éléments si un seul type. Je ne pensais pas quand je me suis référé à Haskell ici. – asjo

+2

Souvent quand on pense qu'ils veulent un zip, c'est pour créer un hash à partir de deux listes. Dans ce cas, mieux vaut utiliser une tranche de hachage. '@hash {@keys} = @ values'. Si ce n'est pas le cas ici, alors désolé pour le bruit. –

Répondre

36

En supposant que vous avez exactement deux listes et ils sont exactement la même longueur, voici une solution à l'origine par Merlyn (Randal Schwartz), qui l'a appelé un effet pervers perlienne:

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

Qu'est-ce qui se passe ici est que pour une Liste de 10 éléments, d'abord, nous trouvons le point de pivot au milieu, dans ce cas 5, et enregistrez-le dans . Ensuite, nous faisons une liste d'indices jusqu'à ce point, dans ce cas 0 1 2 3 4. Ensuite, nous utilisons map pour coupler chaque index avec un autre index qui est à la même distance du point de pivot que le premier index est au début, nous donner (dans ce cas) 0 5 1 6 2 7 3 8 4 9. Ensuite, nous prenons une tranche de @_ en utilisant cela comme la liste des indices. Cela signifie que si 'a', 'b', 'c', 1, 2, 3 est passé à zip2, cette liste sera réorganisée en 'a', 1, 'b', 2, 'c', 3.

Cela peut être écrit en une seule expression le long des lignes de ysth comme ceci:

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

Que vous voudriez utiliser soit la variation dépend de si vous pouvez vous voir se rappeler comment ils fonctionnent, mais pour moi, c'était un expandeur d'esprit.

+0

wow, c'est clair et concis !!! –

+0

+++ 1 intelligent et court – Viet

+0

mon esprit est soufflé! – Richard

27

Le module List::MoreUtils a une fonction zip/maillage qui devrait faire l'affaire:

use List::MoreUtils qw(zip); 

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

my @zipped = zip @numbers, @fruit; 

est ici la source de la fonction de maillage:

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

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

Je ne sais pas comment j'ai réussi à ignorer ce module - merci! – asjo

+2

Quels sont ces at-signs échappés? – dreeves

+1

Prototypes, en disant qu'il prend deux à 32 paramètres de tableau et le sous les recevra implicitement comme arrayrefs. – 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); 

Si les listes sont de longueur différente, cela mettra 'undef' dans les slots supplémentaires mais vous pouvez facilement y remédier si vous ne souhaitez pas le faire. Quelque chose comme (@ l1 [0] & & shift @ l1) le ferait.

Espérons que cela aide!

+1

Belle solution, j'aurais probablement exprimé ma préférence pour ne pas modifier les deux entrées-listes :-) – asjo

2

Algorithm::Loops est vraiment bien si vous faites beaucoup de ce genre de chose.

Mon propre code:

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

L'utilisation de décalages de bits peut être plus rapide dans C mais est simplement une obfuscation inutile dans Perl. Mieux écrit comme ceci: @_ [map {$ _, $ _ + @ _/2} 0 .. (@ _/2 - 1)] Plus court, aussi. –

+1

Ce n'est pas un problème pour la question ici, mais mon zip a été conçu pour fonctionner aussi pour des nombres impairs d'éléments. – ysth

0

Ceci est totalement pas une solution élégante, ni la meilleure solution, loin de l'imagination. Mais c'est amusant!

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 

Comment gérer STORESIZE/PUSH/POP/SHIFT/UNSHIFT/SPLICE est un exercice laissé au lecteur.

10

Pour les tableaux de la même longueur:

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

Très belle solution. Cela prend du temps à le comprendre pour moi. –

+3

Cela pose des problèmes pour les baies de taille inégale. –

12

je trouve la solution suivante simple et facile à lire:

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

Je crois qu'il est aussi plus rapide que les solutions qui créent le tableau dans un mauvais commander d'abord, puis utiliser tranche pour réorganiser, ou des solutions qui modifient @a et @b.

+1

Cela pose des problèmes pour les baies de taille inégale. –

+1

@briandfoy vos commentaires aident, mais vous savez ce que vous avez oublié de faire? indiquer lequel * fonctionne * pour des tableaux de taille inégale. (J'achète celui qui a besoin de faire cela) –

+0

Je n'ai pas oublié. Ce problème dépend de ce que vous voulez faire avec les éléments restants. Vous devriez poser une question différente et spécifier vos contraintes. –

Questions connexes