web-dev-qa-db-fra.com

En Perl, comment créer un hachage dont les clés proviennent d'un tableau donné?

Disons que j'ai un tableau, et je sais que je vais faire beaucoup de "Est-ce que le tableau contient X?" chèques. Pour ce faire, le moyen le plus efficace est de transformer ce tableau en hachage, où les clés sont les éléments du tableau, et vous pouvez simplement dire 

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

Existe-t-il un moyen simple d'effectuer cette conversion de tableau à hachage? Idéalement, il devrait être suffisamment polyvalent pour prendre un tableau anonyme et renvoyer un hachage anonyme.

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

Ce n'est pas aussi court que les solutions "@hash {@array} = ...", mais celles-ci nécessitent que le hachage et le tableau soient déjà définis ailleurs, alors que celle-ci peut prendre un tableau anonyme et renvoyer un hachage anonyme.

Cela permet de prendre chaque élément du tableau et de le lier avec un "1". Lorsque cette liste de paires (clé, 1, clé, 1, clé 1) est affectée à un hachage, les paires impaires deviennent les clés du hachage et les paires paires deviennent les valeurs respectives.

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

C'est une tranche de hachage, une liste de valeurs du hachage, de sorte qu'elle affiche la liste-y @ devant.

De les docs :

Si vous ne savez pas pourquoi vous utilisez un '@' sur une tranche de hachage à la place d'un '%', pense comme ça. Le type de support (carré ou bouclé) gouverne s'il s'agit d'un tableau ou d'un hash étant regardé. De l'autre main, le symbole principal ('$' ou '@') sur le tableau ou le hachage indique si vous récupérez une valeur singulière (un scalaire) ou un pluriel (une liste).

41
moritz
@hash{@keys} = undef;

La syntaxe ici où vous vous référez au hachage avec un @ est une tranche de hachage. Nous disons essentiellement $hash{$keys[0]} AND $hash{$keys[1]} AND $hash{$keys[2]} ... est une liste située à gauche de =, une lvalue, et nous affectons cette liste, qui entre dans le hachage et définit les valeurs de tous les éléments. clés nommées. Dans ce cas, je n’ai spécifié qu’une valeur, de sorte que la valeur entre dans $hash{$keys[0]}, et que les autres entrées de hachage soient toutes vivifiées automatiquement (deviennent actives) avec des valeurs indéfinies. [Ma suggestion initiale ici était de définir l'expression = 1, ce qui aurait défini cette clé sur 1 et les autres sur undef. Je l'ai modifié pour des raisons de cohérence, mais comme nous le verrons ci-dessous, les valeurs exactes importent peu.]

Lorsque vous réalisez que la lvalue, l'expression du côté gauche du =, est une liste construite à partir du hachage, vous comprendrez pourquoi nous utilisons ce @. [Sauf que je pense que cela va changer dans Perl 6.]

L'idée ici est que vous utilisez le hachage comme un ensemble. Ce qui compte, ce n’est pas la valeur que j’attribue; c'est juste l'existence des clés. Donc, ce que vous voulez faire n'est pas quelque chose comme:

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

au lieu:

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

En fait, il est plus efficace de simplement exécuter une vérification exists que de se préoccuper de la valeur du hachage, bien que pour moi l'important soit ici le concept que vous représentez un ensemble uniquement avec les clés du hachage. En outre, quelqu'un a fait remarquer qu'en utilisant undef comme valeur ici, nous consommerions moins d'espace de stockage que nous attribuerions une valeur. (Et génèrent également moins de confusion, car la valeur n'a pas d'importance, et ma solution attribuerait une valeur uniquement au premier élément du hachage et laisserait les autres undef, et certaines autres solutions font tourner les roues pour construire un tableau de valeurs à parcourir. dans le hash; effort complètement perdu).

34
skiphoppy

Notez que si saisir if ( exists $hash{ key } ) n’est pas trop fastidieux pour vous (ce que je préfère utiliser car l’intérêt est vraiment la présence d’une clé plutôt que la vérité de sa valeur),

@hash{@key} = ();
15
Aristotle Pagaltzis

J'ai toujours pensé que 

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

était au moins agréable et lisible/maintenable.

7
Keith

Il y a un présupposé ici, que le moyen le plus efficace de faire beaucoup de "Le tableau contient-il X?" vérifie est de convertir le tableau en un hachage. L'efficacité dépend de la ressource rare, souvent du temps mais parfois de l'espace et parfois du travail du programmeur. Vous doublez au moins la quantité de mémoire utilisée en conservant simultanément une liste et un hachage de la liste. De plus, vous écrivez plus de code original que vous aurez besoin de tester, de documenter, etc.

Vous pouvez également consulter le module List :: MoreUtils, plus précisément les fonctions any(), none(), true() et false(). Ils prennent tous un bloc comme conditionnel et une liste comme argument, similaires à map() et grep():

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

J'ai lancé un test rapide, chargeant dans la moitié de/usr/share/dict/words dans un tableau (25 000 mots), puis recherchant onze mots sélectionnés dans l'ensemble du dictionnaire (chaque 5 000e mot) du tableau, en utilisant à la fois le tableau méthode -to-hash et la fonction any() de List :: MoreUtils.

Sur Perl 5.8.8 générée à partir des sources, la méthode array-to-hash s'exécute presque 1100 fois plus vite que la méthode any() (1300x plus rapidement sous Perl 5.8.7. Empaqueté dans Ubuntu 6.06.)

Ce n'est pas tout, cependant - la conversion de matrice à hachage prend environ 0,04 seconde, ce qui dans ce cas tue l'efficacité temporelle de la méthode de matrice à hachage à 1,5 x 2x plus rapide que la méthode any(). Toujours bon, mais pas aussi stellaire.

Mon sentiment est que la méthode du tableau de hachage va battre any() dans la plupart des cas, mais je me sentirais beaucoup mieux si j'avais des métriques plus solides (beaucoup de tests, d'analyses statistiques décentes, peut-être de gros -O analyse algorithmique de chaque méthode, etc.) En fonction de vos besoins, List :: MoreUtils peut être une meilleure solution. c'est certainement plus flexible et nécessite moins de codage. Rappelez-vous, l'optimisation prématurée est un péché ... :)

7
arclight

Dans Perl 5.10, il y a l'opérateur ~~ proche de la magie:

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

Voir ici: http://dev.Perl.org/Perl5/news/2007/Perl-5.10.0.html

5
RET

À noter également par souci de complétude, ma méthode habituelle pour faire cela avec 2 tableaux de même longueur @keys et @vals que vous préféreriez être un hachage ...

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

5
Tamzin Blake

Vous pouvez aussi utiliser Perl6 :: Junction .

use Perl6::Junction qw'any';

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

if( any(@arr) == 1 ){ ... }
2
Brad Gilbert

La solution de Raldi peut être réduite à cela (le '=>' de l'original n'est pas nécessaire):

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

Cette technique peut également être utilisée pour transformer des listes de texte en hachages:

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

De plus, si vous avez une ligne de valeurs comme celle-ci: "foo = 1, bar = 2, baz = 3" vous pouvez le faire:

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

[EDIT à inclure]


Une autre solution proposée (qui prend deux lignes) est la suivante:

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;
2
Frosty

Si vous faites beaucoup d'opérations théoriques sur les ensembles, vous pouvez aussi utiliser Set :: Scalar ou un module similaire. Alors $s = Set::Scalar->new( @array ) construira l’Ensemble pour vous - et vous pourrez l’interroger avec: $s->contains($m).

1
zby

Vous pouvez placer le code dans un sous-programme si vous ne voulez pas polluer votre espace de noms.

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

Ou même mieux:

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;

Si vous voulez vraiment passer une référence à un tableau:

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

my @key_list = qw'one two three';
my $hash_ref = keylist @key_list;
1
Brad Gilbert
#!/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);

donne (notez que les touches répétées obtiennent la valeur à la position la plus grande dans le tableau - 8 -> 2 et non 6)

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

Vous pouvez également vouloir vérifier Tie :: IxHash , qui implémente des tableaux associatifs ordonnés. Cela vous permettrait de faire les deux types de recherches (hachage et index) sur une copie de vos données.

0
Dave G