Pregunta

Recientemente "necesitaba" una función zip en Perl 5 (mientras pensaba en ¿Cómo calculo el tiempo relativo?), es decir.una función que toma dos listas y las "comprime" en una lista, entrelazando los elementos.

(Pseudo)ejemplo:

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

Haskell tiene éxito en el Preludio y Perl 6 tiene un operador zip integrado, pero ¿cómo se hace de forma elegante en Perl 5?

¿Fue útil?

Solución

Suponiendo que tiene exactamente dos listas y que tienen exactamente la misma longitud, aquí hay una solución originalmente de merlyn (Randal Schwartz), quien la llamó perversamente perlish:

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

Lo que sucede aquí es que para una lista de 10 elementos, primero encontramos el punto de pivote en el medio, en este caso 5, y lo guardamos en $p.Luego hacemos una lista de índices hasta ese punto, en este caso 0 1 2 3 4.A continuación usamos map para emparejar cada índice con otro índice que esté a la misma distancia del punto de pivote que el primer índice desde el principio, lo que nos da (en este caso) 0 5 1 6 2 7 3 8 4 9.Luego tomamos un trozo de @_ usando eso como la lista de índices.Esto significa que si 'a', 'b', 'c', 1, 2, 3 se pasa a zip2, devolverá esa lista reorganizada en 'a', 1, 'b', 2, 'c', 3.

Esto se puede escribir en una sola expresión siguiendo las líneas de ysth así:

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

Si desea utilizar cualquiera de las variaciones depende de si puede verse recordando cómo funcionan, pero para mí, fue una expansión mental.

Otros consejos

El Lista::MásUtilidades El módulo tiene una función zip/mesh que debería funcionar:

use List::MoreUtils qw(zip);

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

my @zipped = zip @numbers, @fruit;

Aquí está la fuente de la función de malla:

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

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

La siguiente solución me parece sencilla y fácil de leer:

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

Creo que también es más rápido que las soluciones que primero crean la matriz en un orden incorrecto y luego usan el segmento para reordenar, o las soluciones que modifican @a y @b.

Para matrices de la misma longitud:

my @zipped = ( @a, @b )[ map { $_, $_ + @a } ( 0 .. $#a ) ];
my @l1 = qw/1 2 3/;
my @l2 = qw/7 8 9/;
my @out; 
push @out, shift @l1, shift @l2 while ( @l1 || @l2 );

Si las listas tienen una longitud diferente, esto colocará 'undef' en los espacios adicionales, pero puede remediarlo fácilmente si no desea hacerlo.Algo como ( @l1[0] && shift @l1 ) lo haría.

¡Espero que esto ayude!

Algorithm::Loops Es realmente bueno si haces mucho de este tipo de cosas.

Mi propio código:

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

Esta no es en absoluto una solución elegante, ni tampoco es la mejor solución desde ningún punto de vista de la imaginación.¡Pero es divertido!

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

Cómo manejar STORESIZE/PUSH/POP/SHIFT/UNSHIFT/SPLICE Es un ejercicio que se deja al lector.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top