2013-05-26 9 views
17

Voglio unire due array di uguale lunghezza in un singolo array prendendo il primo elemento dall'array A, il primo elemento dall'array B; secondo elemento da A, secondo elemento da B, ecc Il seguente programma illustra l'algoritmo:Perl - funzione integrata per "zippare" insieme due array?

# file zipper.pl 
use strict; 
use warnings; 
use 5.010; 

my @keys = qw/abel baker charlie dog easy fox/; 
my @values = qw/a b c d e f/; 

# ==> Is there a builtin function that is equivalent of zipper()? <== 
# 
my %hash = zipper(\@keys, \@values); 

while (my ($k, $v) = each %hash) { 
    say "$k=$v"; 
} 

# zipper(): Take two equal-length arrays and merge them (one from A, one from B, 
# another from A, another from B, etc.) into a single array. 
# 
sub zipper { 
    my $k_ref = shift; 
    my $v_ref = shift; 
    die "Arrays must be equal length" if @$k_ref != @$v_ref; 
    my $i = 0; 
    return map { $k_ref->[ $i++ ], $_ } @$v_ref; 
} 

uscita

$ ./zipper.pl 
easy=e 
dog=d 
fox=f 
charlie=c 
baker=b 
abel=a 

mi chiedo se ho trascurato una funzione built-in Perl che fai l'equivalente di zipper(). Sarà nel ciclo più interno del programma e dovrà essere eseguito il più velocemente possibile. Se non c'è un modulo built-in o CPAN, qualcuno può migliorare la mia implementazione?

risposta

24

Altri hanno dato buone risposte per lato della maglia/zip della questione, ma se si sta solo creando un hash da una serie di chiavi e uno di valori puoi farlo con poco apprezzato hash slice.

#!/usr/bin/env perl 

use strict; 
use warnings; 

my @keys = qw/abel baker charlie dog easy fox/; 
my @values = qw/a b c d e f/; 

my %hash; 
@hash{@keys} = @values; 

use Data::Dumper; 
print Dumper \%hash; 

Addendum

ho avuto modo di pensare il motivo per cui si può scegliere un metodo rispetto all'altro. Personalmente ritengo che l'implementazione della slice sia leggibile come lo zip, ma altri potrebbero non essere d'accordo. Se lo fai spesso, potresti preoccuparti della velocità, nel qual caso la forma della fetta è più veloce.

#!/usr/bin/env perl 

use strict; 
use warnings; 

use List::MoreUtils qw/zip/; 
use Benchmark qw/cmpthese/; 

my @keys = qw/abel baker charlie dog easy fox/; 
my @values = qw/a b c d e f/; 

cmpthese(100000, { 
    zip => sub { 
    my %hash = zip @keys, @values; 
    }, 
    slice => sub { 
    my %hash; 
    @hash{@keys} = @values; 
    }, 
}); 

risultati:

  Rate zip slice 
zip 51282/s -- -34% 
slice 78125/s 52% -- 
+0

Davvero molto bello. E come al solito, invece di porre la vera domanda, ho assunto una soluzione e poi ho formulato la domanda in questo modo. La tua soluzione è senza dubbio il modo migliore per andare. Ulteriori rivelazioni: sto effettivamente cercando di inizializzare un Tie :: IxHash - Ho usato il modulo OO, e non credo che supporti la slice hash, ma per i miei scopi penso che la semplice interfaccia non-OO sarebbe Stammi bene. – Chap

+0

Devo anche dire, so che molte persone non amano il modo in cui i sigilli cambiano in Perl, ma penso che in questo caso aiuti davvero ad ispezionare visivamente ciò che sta accadendo. Forse è solo io :-) –

11

Dato che hai offerto un'idea CPAN, c'è List::MoreUtils e zip.

use List::MoreUtils qw(zip); 

my @keys = qw/abel baker charlie dog easy fox/; 
my @values = qw/a b c d e f/; 

my @zipped = zip @keys, @values; 

Il contenuto di @zipped sarebbero:

abel, a, baker, b, charlie, c, dog, d, easy, e, fox, f 

La parte bella sull'utilizzo di questo metodo è che si può comprimere più di due liste, se lo si desidera. Dal momento che Perl non ha il concetto di un tipo di tupla, è quasi come un'operazione di appiattimento.

+0

sto combinando chiavi e valori in un singolo array, che quando viene assegnato ad un hash inizializza l'hash di queste coppie chiave = valore. In realtà assegnerò semplicemente la lista restituita da zip direttamente all'hash. – Chap

+0

Puoi farlo. 'my% hash = zip @keys, @values;'. Ma se stai facendo un hash, perché non usare solo una fetta? 'mio% hash; @hash {@keys} = @values; ' – friedo

+0

nm, ho appena visto la risposta di Joel. – friedo

5

Sebbene questa specifica funzione esiste già in List :: moreutils, è possibile utilizzare i prototipi per dare le proprie funzioni per gli array la comparsa di operatori di matrice built-in (come push, shift, pop):

sub zipper (++) { # perldoc perlsub 
    my ($k, $v) = @_; 
    die "Arrays must be equal length" if @$k != @$v; 
    my $i; 
    return map { $k->[$i++], $_ } @$v 
} 

%hash = zipper @keys, @values; 
%hash = zipper \@keys, \@values; 
%hash = zipper $key_aref, $value_aref; 
+2

Una nota sul prototipo: concordo sul fatto che '+' è preferibile a '\ @', ma il primo è disponibile solo dalla v14. Il prototipo '+' accetta anche qualsiasi scalare, o prende un riferimento quando viene presentata una variabile '@ array' o'% hash'. Potrebbe essere più pulito non usarlo qui. – amon

+0

@amon, dal momento che i prototipi non riguardano rigidità o typechecking in Perl, non mi interessa la possibilità di un hash. Ma il requisito della versione è divertente - utilizzo solo systemperl in one-liners, quindi ho dimenticato di considerare quando è stata aggiunta questa funzionalità. –