2008-09-18 9 views
71

Diciamo che ho un array, e so che sto andando a fare un sacco di "L'array contiene X?" controlli. Il modo efficace per farlo è trasformare l'array in un hash, dove le chiavi sono gli elementi dell'array, quindi puoi semplicemente dire In Perl, come posso creare un hash le cui chiavi provengono da un determinato array?

if($hash{X}) { ... }

C'è un modo semplice per fare questa conversione da matrice ad hash? Idealmente, dovrebbe essere abbastanza versatile da prendere un array anonimo e restituire un hash anonimo.

risposta

106
%hash = map { $_ => 1 } @array; 

non è così breve come il "@hash {} = @array ..." soluzioni, ma quelli richiedono l'hash ed array da già definito da qualche altra parte, che tale si può prendere un array anonimo e restituire un hash anonimo.

Ciò che fa è prendere ogni elemento nella matrice e associarlo con un "1". Quando questa lista di chiavi (chiave, 1, chiave, 1, chiave 1) viene assegnata a un hash, quelle dispari diventano le chiavi dell'hash, e quelle pari diventano i rispettivi valori.

34
@hash{@keys} = undef; 

La sintassi qui, dove si fa riferimento alla hash con una @ è una fetta di hash. In pratica stiamo dicendo $hash{$keys[0]} E $hash{$keys[1]} E $hash{$keys[2]} ... è una lista sul lato sinistro di =, un lvalue, e stiamo assegnando a quella lista, che in realtà va nell'hash e imposta i valori per tutti chiavi nominate. In questo caso, ho specificato solo un valore, in modo che il valore vada in $hash{$keys[0]} e le altre voci di hash tutte vivano automaticamente (prendono vita) con valori indefiniti. [Il mio suggerimento originale qui era impostare expression = 1, che avrebbe impostato quella chiave a 1 e gli altri a undef. L'ho modificato per coerenza, ma come vedremo in seguito, i valori esatti non contano.]

Quando ti accorgi che il lvalue, l'espressione sul lato sinistro del =, è un elenco costruito da l'hash, quindi inizierà a dare un senso al motivo per cui stiamo usando quello @. [Tranne che penso che questo cambierà in Perl 6.]

L'idea è che si sta utilizzando l'hash come set. Ciò che conta non è il valore che sto assegnando; è solo l'esistenza delle chiavi. Allora, cosa si vuole fare, non è qualcosa di simile:

if ($hash{$key} == 1) # then key is in the hash 

invece:

if (exists $hash{$key}) # then key is in the set 

in realtà è più efficiente per eseguire solo un controllo exists piuttosto che perdere tempo con il valore del hash, anche se a me la cosa importante qui è solo il concetto che stai rappresentando un set solo con le chiavi dell'hash. Inoltre, qualcuno ha sottolineato che usando lo undef come valore qui, consumeremo meno spazio di archiviazione di quanto assegneremmo un valore. (E anche generare meno confusione, dato che il valore non ha importanza, e la mia soluzione assegnerebbe un valore solo al primo elemento dell'hash e lascerà gli altri undef, e alcune altre soluzioni stanno trasformando i carrelli per costruire una serie di valori nell'hashish, sforzo completamente sprecato).

+1

questo è preferibile rispetto all'altro perché non crea una lista temporanea per inizializzare l'hash. Questo dovrebbe essere più veloce e consumare meno memoria. –

+0

Questo non funziona quando testato: test.pl: my @arr = ("foo", "bar", "baz"); my @hash {@arr} = 1; Errore di sintassi alla riga test.pl 2, vicino a "@hash {" – Frosty

+1

Frosty: devi prima dichiarare "my% hash", quindi fare "@hash {@arr} = 1" (no "my"). –

2

soluzione Raldi può essere stretto fino a questo (il '=>' dal l'originale non è necessario):

my %hash = map { $_,1 } @array; 

Questa tecnica può essere utilizzata anche per trasformare le liste di testo in hash:

my %hash = map { $_,1 } split(",",$line) 

Inoltre se si dispone di una linea di valori in questo modo: "foo = 1, bar = 2, baz = 3" si può fare questo:

my %hash = map { split("=",$_) } split(",",$line); 

[EDIT per includere]


Un'altra soluzione offerta (che prende due linee) è:

my %hash; 
#The values in %hash can only be accessed by doing exists($hash{$key}) 
#The assignment only works with '= undef;' and will not work properly with '= 1;' 
#if you do '= 1;' only the hash key of $array[0] will be set to 1; 
@hash{@array} = undef; 
+1

Il diverso tra $ _ => 1 e $ _, 1 è puramente stilistico. Personalmente preferisco => come sembra indicare il link chiave/valore in modo più esplicito. La tua soluzione @hash {@array} = 1 non funziona. Solo uno dei valori (quello associato alla prima chiave in @array) viene impostato su 1. –

+0

Grazie per i chiarimenti. Ho modificato la risposta. – Frosty

38
@hash{@array} = (1) x @array; 

Si tratta di una fetta di hash, un elenco di valori di hash, quindi diventa la lista-y @ davanti.

Da the docs:

Se siete confusi sul motivo per cui si utilizza un '@' lì su una fetta di hash invece di un '%', pensa a come questo. Il tipo di staffa (quadrato o riccio) controlla se si tratta di un array o di un hash . Dall'altra parte , il simbolo iniziale ('$' o '@') nell'array o hash indica se si sta recuperando un valore singolare (uno scalare) o un plurale (un elenco).

+1

Wow, non ho mai sentito di (o pensato a) quello. Grazie! Ho difficoltà a capire come funziona. Puoi aggiungere una spiegazione? In particolare, come si può prendere un hash chiamato% hash e fare riferimento ad esso con un segno @? – raldi

+1

raldi: è una porzione di hash, una lista di valori dall'hash, quindi ha in primo piano l'elenco-y @. Vedi http://perldoc.perl.org/perldata.html#Slices - in particolare l'ultimo paragrafo della sezione – ysth

+0

Devi aggiungere questo alla tua risposta! – raldi

2

È inoltre possibile utilizzare Perl6::Junction.

use Perl6::Junction qw'any'; 

my @arr = (1, 2, 3); 

if(any(@arr) == 1){ ... } 
+0

Se lo si fa più volte per un array di grandi dimensioni, potrebbe essere molto più lento. – ysth

+0

Effettivamente farlo una volta è molto più lento. deve creare un oggetto. Quindi poco dopo, distruggerà quell'oggetto. Questo è solo un esempio di ciò che è possibile. –

5

in Perl 5.10, c'è l'operatore close-to-magic ~~:

sub invite_in { 
    my $vampires = [ qw(Angel Darla Spike Drusilla) ]; 
    return ($_[0] ~~ $vampires) ? 0 : 1 ; 
} 

Vedi qui: http://dev.perl.org/perl5/news/2007/perl-5.10.0.html

+0

Se lo si fa più volte per un array di grandi dimensioni, potrebbe essere molto più lento. – ysth

+1

Sarebbe il "gestore smart match" :) –

14

Nota che se digitando if (exists $hash{ key }) non è troppo lavoro per voi (che preferisco usare poiché la questione di interesse è in realtà la presenza di una chiave piuttosto che la verità del suo valore), allora puoi usare il breve e dolce

@hash{@key} =(); 
7

C'è un presupposto qui, che il modo più efficiente per fare un sacco di "L'array contiene X?" controlla è per convertire la matrice in un hash. L'efficienza dipende dalla risorsa scarsa, spesso il tempo ma a volte lo spazio e talvolta lo sforzo del programmatore. Stai almeno raddoppiando la memoria consumata tenendo contemporaneamente una lista e un hash della lista. Inoltre si sta scrivendo codice originale più che avrete bisogno di testare, documenti, ecc

In alternativa, guardare il modulo Lista :: moreutils, in particolare le funzioni any(), none(), true() e false().Tutti prendono un blocco come il condizionale e una lista come argomento, simile a map() e grep():

print "At least one value undefined" if any { !defined($_) } @list;

ho eseguito un test rapido, il carico a metà di/usr/share/dict/parole per un array (25000 parole), quindi cercando undici parole selezionate nell'intero dizionario (ogni 5000 ° parola) nell'array, utilizzando sia il metodo array-to-hash che la funzione any() da List :: MoreUtils.

Sul Perl 5.8.8 installato dal codice sorgente, il metodo array-to-hash corre quasi 1100X più veloce del metodo any() (1300X più veloce sotto Ubuntu 6.06 del confezionato Perl 5.8.7.)

Questo non è il storia completa tuttavia: la conversione da array ad hash richiede circa 0,04 secondi, che in questo caso elimina l'efficienza temporale del metodo array-to-hash a 1,5x-2x più veloce rispetto al metodo any(). Comunque buono, ma non così stellare.

Il mio istinto è che il metodo array-to-hash sta andando a battere any() nella maggior parte dei casi, ma mi sentirei molto meglio se avessi alcune metriche più solide (un sacco di casi di test, analisi statistiche decenti , forse qualche analisi algoritmica big-O di ogni metodo, ecc.) A seconda delle esigenze, List :: MoreUtils potrebbe essere una soluzione migliore; è sicuramente più flessibile e richiede meno codice. Ricorda, l'ottimizzazione prematura è un peccato ... :)

+0

Elenco :: MoreUtils è un ottimo consiglio, grazie. – SquareCog

0

Si potrebbe anche voler verificare Tie::IxHash, che implementa array associativi ordinati. Ciò consentirebbe di eseguire entrambi i tipi di ricerche (hash e indice) su una copia dei dati.

1

Se si eseguono molte operazioni teoriche, è possibile utilizzare anche il modulo Set::Scalar o simile. Quindi lo $s = Set::Scalar->new(@array) creerà il Set per te - e potrai richiederlo con: $s->contains($m).

6

Ho sempre pensato che

foreach my $item (@array) { $hash{$item} = 1 } 

era almeno bello e leggibile/mantenibile.

1

È possibile inserire il codice in una subroutine, se non si desidera inquinare il proprio spazio dei nomi.

my $hash_ref = 
    sub{ 
    my %hash; 
    @hash{ @{[ qw'one two three' ]} } = undef; 
    return \%hash; 
    }->(); 

O ancora meglio:

sub keylist(@){ 
    my %hash; 
    @hash{@_} = undef; 
    return \%hash; 
} 

my $hash_ref = keylist qw'one two three'; 

# or 

my @key_list = qw'one two three'; 
my $hash_ref = keylist @key_list; 

Se si voleva davvero passare un riferimento ad array:

sub keylist(\@){ 
    my %hash; 
    @hash{ @{$_[0]} } = undef if @_; 
    return \%hash; 
} 

my @key_list = qw'one two three'; 
my $hash_ref = keylist @key_list; 
+0

'% hash = map {$ _, undef} @ keylist' –

0
#!/usr/bin/perl -w 

use strict; 
use Data::Dumper; 

my @a = qw(5 8 2 5 4 8 9); 
my @b = qw(7 6 5 4 3 2 1); 
my $h = {}; 

@{$h}{@a} = @b; 

print Dumper($h); 

dà (notare i tasti ripetuti ottenere il valore alla massima posizione di nell'array - cioè 8-> 2 e non 6)

$VAR1 = { 
      '8' => '2', 
      '4' => '3', 
      '9' => '1', 
      '2' => '5', 
      '5' => '4' 
     }; 
+0

Un hasref sembra più che un po 'esagerato qui. – bobbogo

2

degno di nota anche per la completezza, il mio solito metodo per fare questo con 2 array stessa lunghezza @keys e @vals cui si preferirebbe fosse un hash ...

my %hash = map { $keys[$_] => $vals[$_] } ([email protected]);

+4

Il solito idioma per '@ keys-1' è' $ # keys'. –

+0

@StefanMajewsky Non ho visto quello effettivamente usato in un istante. Me ne sto lontano da me - è brutto. –