Domanda

Di recente ho "bisogno" di una zip in Perl 5 (mentre stavo pensando Come faccio a calcolare il tempo relativo?), cioèuna funzione che prende due liste e delle "cerniere" insieme a un elenco, sovrapposizione di elementi.

(Pseudo)esempio:

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

Haskell ha zip nel Preludio e Perl 6 ha una zip operatore costruito nel, ma come si fa a fare in modo elegante in Perl 5?

È stato utile?

Soluzione

Supponendo di avere esattamente due liste e sono esattamente la stessa lunghezza, qui è una soluzione originariamente da merlyn (Randal Schwartz), che lo ha definito un meccanismo perverso perlish:

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

Quello che succede qui è che per 10 elenco di elementi, in primo luogo, troviamo il punto di perno nel mezzo, in questo caso 5, e salvarlo in $p.Allora facciamo una lista di indici fino a che punto, in questo caso 0 1 2 3 4.Prossimo usiamo map per associare ogni indice con un altro indice che è alla stessa distanza dal punto di rotazione come il primo indice è stato fin dall'inizio, dando a noi (in questo caso) 0 5 1 6 2 7 3 8 4 9.Poi prendiamo una fetta @_ usare questo come un elenco di indici.Questo significa che se 'a', 'b', 'c', 1, 2, 3 è passato zip2, tornerà l'elenco riorganizzati in 'a', 1, 'b', 2, 'c', 3.

Questo può essere scritto in una singola espressione insieme ysth linee in questo modo:

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

Se si desidera utilizzare la variazione dipende dal fatto che si può vedere voi stessi di ricordare come funzionano, ma per me è stata una mente expander.

Altri suggerimenti

Il Lista::MoreUtils il modulo ha una zip/mesh funzione che dovrebbe fare il trucco:

use List::MoreUtils qw(zip);

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

my @zipped = zip @numbers, @fruit;

Qui è la fonte della maglia funzione:

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

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

Trovo la seguente soluzione semplice e di facile lettura:

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

Io credo che sia anche più veloce rispetto alle soluzioni che creare l'array in un ordine diverso da prima e quindi utilizzare fetta di riordinare, o soluzioni che modificano @a e @b.

Per le matrici della stessa lunghezza:

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 le liste sono di lunghezza diversa, questo metterà 'undef' in slot extra, ma si può facilmente porre rimedio se non si vuole fare questo.Qualcosa di simile ( @l1[0] && shift @l1 ) dovrebbe fare.

Spero che questo aiuta!

Algorithm::Loops è veramente bello, se non molto di questo genere di cose.

Il mio codice:

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

Questo non è del tutto una soluzione elegante, e non è la soluzione migliore da qualsiasi tratto di immaginazione.Ma è divertente!

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

Come gestire STORESIZE/PUSH/POP/SHIFT/UNSHIFT/SPLICE è un esercizio lasciato al lettore.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top