Pergunta

Recentemente eu "precisava" de um zip função em Perl 5 (enquanto eu estava pensando Como faço para calcular o tempo relativo?), i.é.uma função que receba duas listas e "zips", para uma lista de intercalação de elementos.

(Pseudo)exemplo:

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

Haskell tem de correr no Prelúdio e Perl 6 tem um fecho de correr operador construído em, mas como fazer isso de uma forma elegante em Perl 5?

Foi útil?

Solução

Supondo que você tenha exatamente duas listas e eles são exatamente o mesmo comprimento, aqui está uma solução originalmente por merlyn (Randal Schwartz), que a chamou, perversamente, perlish:

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

O que acontece aqui é que, para uma 10-elemento de lista, em primeiro lugar, encontramos o ponto de articulação no meio, neste caso 5, e salvá-lo em $p.Em seguida, fazemos uma lista de índices até que ponto, neste caso 0 1 2 3 4.Em seguida, use map a par de cada índice com outro índice que está à mesma distância do ponto de articulação como o primeiro índice é, desde o início, dando-nos (neste caso) 0 5 1 6 2 7 3 8 4 9.Em seguida, tomamos uma fatia de @_ usando isso como a lista de índices.Isso significa que, se 'a', 'b', 'c', 1, 2, 3 é passado para zip2, ele vai retornar lista rearranjados em 'a', 1, 'b', 2, 'c', 3.

Isso pode ser escrito em uma única expressão, juntamente ysth linhas assim:

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

Se você gostaria de usar qualquer variação depende se você pode ver você se lembrar de como eles funcionam, mas, para mim, era uma mente expansor.

Outras dicas

O Lista::MoreUtils módulo tem um fecho de correr/de malha função que deve fazer o truque:

use List::MoreUtils qw(zip);

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

my @zipped = zip @numbers, @fruit;

Aqui está a fonte da malha função:

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

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

Eu acho o seguinte solução simples e de fácil leitura:

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

Eu acredito que é também mais rápido do que as soluções que criar a matriz em uma ordem errada primeiro e, em seguida, usar fatia para reordenar ou soluções que modificar @a e @b.

Para matrizes de mesmo comprimento:

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 );

Se as listas são um comprimento diferente, isso vai colocar 'undef" no slots extras, mas você pode facilmente corrigir isso se você não quiser fazer isso.Algo como ( @l1[0] && shift @l1 ) iria fazê-lo.

Espero que isso ajude!

Algorithm::Loops é muito bom se você fazer esse tipo de coisa.

Meu próprio código:

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

Isso é totalmente não é uma solução elegante, nem é a melhor solução por nenhum estiramento da imaginação.Mas é 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

Como lidar com STORESIZE/PUSH/POP/SHIFT/UNSHIFT/SPLICE é um exercício e deixamos para o leitor.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top