In Perl, come posso creare un hash le cui chiavi provengono da un determinato array?

StackOverflow https://stackoverflow.com/questions/95820

  •  01-07-2019
  •  | 
  •  

Domanda

Diciamo che ho un array e so che farò un sacco di "l'array contiene X?" controlli.Il modo efficiente per farlo è trasformare l'array in un hash, dove le chiavi sono gli elementi dell'array, e quindi puoi semplicemente dire

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

Esiste un modo semplice per eseguire questa conversione da array a hash?Idealmente, dovrebbe essere abbastanza versatile da prendere un array anonimo e restituire un hash anonimo.

È stato utile?

Soluzione

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

Non è breve come le soluzioni "@hash{@array} = ...", ma quelle richiedono che l'hash e l'array siano già definiti da qualche altra parte, mentre questa può prendere un array anonimo e restituire un hash anonimo.

Ciò che fa è prendere ogni elemento dell'array e accoppiarlo con un "1".Quando questo elenco di coppie (chiave, 1, chiave, 1, chiave 1) viene assegnato a un hash, quelle con numeri dispari diventano le chiavi dell'hash e quelle con numeri pari diventano i rispettivi valori.

Altri suggerimenti

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

È una porzione di hash, un elenco di valori dell'hash, quindi ha la lista-y @ davanti.

Da i documenti:

Se sei confuso sul perché usi un "@" lì su una fetta hash invece di un "%", pensaci così.Il tipo di parentesi (quadrato o riccio) regola se si tratta di un array o di un hash.D'altra parte, il simbolo principale ('$' o '@') sull'array o sull'hash indica se si sta riprendendo un valore singolare (uno scalare) o uno plurale (un elenco).

@hash{@keys} = undef;

La sintassi qui in cui ti riferisci all'hash con an @ è una fetta di hashish.Fondamentalmente 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 tutte le chiavi denominate.In questo caso, ho specificato solo un valore, quindi quel valore entra in $hash{$keys[0]}, e le altre voci hash si auto-vivificano (prendono vita) con valori indefiniti.[Il mio suggerimento originale qui era di impostare l'espressione = 1, che avrebbe impostato quella chiave su 1 e le altre su undef.L'ho modificato per coerenza, ma come vedremo di seguito, i valori esatti non contano.]

Quando ti rendi conto che lvalue, l'espressione sul lato sinistro di =, è una lista costruita a partire dall'hash, allora inizierà ad avere un senso il motivo per cui lo stiamo usando @.[Tranne che penso che questo cambierà in Perl 6.]

L'idea qui è che stai usando l'hash come un set.Ciò che conta non è il valore che sto assegnando;è solo l'esistenza delle chiavi.Quindi quello che vuoi fare non è qualcosa del tipo:

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 eseguire semplicemente un file exists check piuttosto che preoccuparsi del valore nell'hash, anche se per me la cosa importante qui è solo il concetto che stai rappresentando un insieme solo con le chiavi dell'hash.Inoltre, qualcuno lo ha sottolineato utilizzando undef come valore qui, consumeremo meno spazio di archiviazione di quello che utilizzeremmo assegnando un valore.(E genererebbe anche meno confusione, poiché il valore non ha importanza, e la mia soluzione assegnerebbe un valore solo al primo elemento dell'hash e lascerebbe gli altri undef, e alcune altre soluzioni stanno girando le ruote per creare una serie di valori da inserire nell'hash;fatica completamente sprecata).

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

@hash{@key} = ();

C'è un presupposto qui, che il modo più efficiente per fare un sacco di "l'array contiene X?" Controlli è convertire l'array in un hash.L'efficienza dipende dalle risorse scarse, spesso dal tempo ma a volte dallo spazio e talvolta dallo sforzo del programmatore.Stai almeno raddoppiando la memoria consumata mantenendo contemporaneamente un elenco e un hash dell'elenco.Inoltre stai scrivendo più codice originale che dovrai testare, documentare, ecc.

In alternativa, guarda il modulo List::MoreUtils, in particolare le funzioni any(), none(), true() E false().Prendono tutti un blocco come condizionale e una lista come argomento, in modo simile a map() E grep():

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

Ho eseguito un test rapido, caricando metà di /usr/share/dict/words in un array (25.000 parole), quindi cercando undici parole selezionate dall'intero dizionario (ogni 5.000 parole) nell'array, utilizzando sia l'array -to-hash e il metodo any() funzione da List::MoreUtils.

Su Perl 5.8.8 compilato dal codice sorgente, il metodo array-to-hash viene eseguito quasi 1100 volte più velocemente del metodo any() metodo (1300 volte più veloce con Perl 5.8.7 in pacchetto Ubuntu 6.06.)

Tuttavia, questa non è la storia completa: la conversione da array a hash richiede circa 0,04 secondi, il che in questo caso riduce l'efficienza temporale del metodo da array a hash a 1,5x-2x più veloce del metodo any() metodo.Ancora buono, ma non altrettanto stellare.

La mia sensazione istintiva è che il metodo array-to-hash sarà migliore any() nella maggior parte dei casi, ma mi sentirei molto meglio se avessi parametri più solidi (molti casi di test, analisi statistiche decenti, forse qualche analisi algoritmica con O grande di ciascun metodo, ecc.). A seconda delle tue esigenze, Elenco ::MoreUtils potrebbe essere una soluzione migliore;è sicuramente più flessibile e richiede meno codifica.Ricorda, l'ottimizzazione prematura è un peccato...:)

L'ho sempre pensato

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

era almeno carino e leggibile/manutenibile.

In perl 5.10 c'è l'operatore quasi magico ~~:

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

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

Vale anche la pena notare, per completezza, il mio solito metodo per farlo con 2 array della stessa lunghezza @keys E @vals che preferiresti fosse un hash...

my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);

La soluzione di Raldi può essere ridotta a questo (il '=>' dell'originale non è necessario):

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

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

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

Inoltre, se hai una riga di valori come questa:"foo=1,bar=2,baz=3" puoi farlo:

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

[MODIFICA per includere]


Un'altra soluzione offerta (che richiede due righe) è:

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;

Potresti anche usare Perl6::Giunzione.

use Perl6::Junction qw'any';

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

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

Se esegui molte operazioni teoriche degli insiemi, puoi anche utilizzare Imposta::Scalare o modulo simile.Poi $s = Set::Scalar->new( @array ) costruirà il Set per te - e potrai interrogarlo con: $s->contains($m).

Puoi inserire il codice in una subroutine, se non vuoi inquinare il tuo 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 volessi davvero passare un riferimento all'array:

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

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

Potresti anche voler dare un'occhiata Cravatta::IxHash, che implementa array associativi ordinati.Ciò ti consentirebbe di eseguire entrambi i tipi di ricerche (hash e indice) su una copia dei tuoi dati.

#!/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à (nota che i tasti ripetuti ottengono il valore nella posizione massima nell'array, ovvero 8->2 e non 6)

$VAR1 = {
          '8' => '2',
          '4' => '3',
          '9' => '1',
          '2' => '5',
          '5' => '4'
        };
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top