it-swarm.com.de

Wie erstelle ich in Perl einen Hash, dessen Schlüssel aus einem gegebenen Array stammen?

Nehmen wir an, ich habe ein Array und weiß, dass ich viel tun werde "Enthält das Array X?" prüft. Der effizienteste Weg, dies zu tun, besteht darin, dieses Array in einen Hash zu verwandeln, wobei die Schlüssel die Elemente des Arrays sind 

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

Gibt es eine einfache Möglichkeit, diese Array-zu-Hash-Konvertierung durchzuführen? Im Idealfall sollte es vielseitig genug sein, um ein anonymes Array zu verwenden und einen anonymen Hash zurückzugeben.

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

Es ist nicht so kurz wie die "@hash {@array} = ..." -Lösungen, aber diese erfordern, dass der Hash und das Array bereits an anderer Stelle definiert werden, wohingegen diese eine anonyme Matrix nehmen und einen anonymen Hash zurückgeben kann.

Das bedeutet, dass jedes Element im Array mit einer "1" gekoppelt wird. Wenn diese Liste von (Schlüssel, 1, Schlüssel, 1, Schlüssel 1) -Paaren einem Hash zugewiesen wird, werden die ungeradzahligen zu den Hash-Schlüsseln und die geradzahligen zu den jeweiligen Werten.

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

Es ist ein Hash-Slice, eine Liste von Werten aus dem Hash, also wird das list-y @ vorne angezeigt.

Von den Dokumenten :

Wenn Sie verwirrt sind, warum Sie .__ verwenden. stattdessen ein '@' in einem Hash-Slice von einem '%', so denken Sie daran. Das Art der Halterung (eckig oder lockig) legt fest, ob es sich um ein Array oder ein .__ handelt. Haschisch angeschaut. Auf dem anderen Hand das führende Symbol ('$' oder '@') auf dem Array oder Hash zeigt an, ob Sie erhalten einen singulären Wert zurück (ein Skalar) oder ein Plural (eine Liste).

41
moritz
@hash{@keys} = undef;

Die Syntax, bei der Sie sich auf den Hash mit einem @ beziehen, ist ein Hash-Slice. Wir sagen im Grunde $hash{$keys[0]} AND $hash{$keys[1]} AND $hash{$keys[2]} ... ist eine Liste auf der linken Seite des =, ein lvalue, und wir weisen diese Liste zu, die tatsächlich in den Hash-Wert geht und die Werte für alle Werte setzt benannte Schlüssel. In diesem Fall habe ich nur einen Wert angegeben, so dass dieser Wert in $hash{$keys[0]} eingeht, und die anderen Hash-Einträge werden alle automatisch mit unbestimmten Werten belebt. [Mein ursprünglicher Vorschlag hier war der Ausdruck = 1, der den einen Schlüssel auf 1 und die anderen auf undef gesetzt hätte. Ich habe es aus Gründen der Konsistenz geändert, aber wie wir weiter unten sehen werden, spielen die genauen Werte keine Rolle.]

Wenn Sie feststellen, dass der Wert lvalue, der Ausdruck auf der linken Seite von =, eine aus dem Hash aufgebaute Liste ist, wird es einen Sinn ergeben, warum wir diesen @ verwenden. [Außer ich denke, das wird sich in Perl 6 ändern.]

Die Idee hier ist, dass Sie den Hash als Set verwenden. Was zählt, ist nicht der Wert, den ich einsetze; es ist nur die Existenz der Schlüssel. Also, was Sie wollen, ist nicht so etwas wie:

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

stattdessen:

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

Es ist tatsächlich effizienter, einfach eine exists-Prüfung auszuführen, als sich mit dem Wert im Hash zu beschäftigen, obwohl für mich das Wichtigste nur das Konzept ist, dass Sie einen Satz nur mit den Schlüsseln des Hash darstellen. Jemand hat auch darauf hingewiesen, dass durch die Verwendung von undef als Wert hier weniger Speicherplatz verbraucht wird, als wir einen Wert zuweisen würden. (Und auch weniger Verwirrung erzeugen, da der Wert keine Rolle spielt, und meine Lösung würde nur dem ersten Element im Hash einen Wert zuweisen und die anderen Variablen undef belassen in den Hash, völlig vergeudete Anstrengung).

34
skiphoppy

Beachten Sie, dass die Eingabe von if ( exists $hash{ key } ) für Sie nicht zu aufwendig ist (was ich lieber benutze, da es wirklich um das Vorhandensein eines Schlüssels als um die Wahrhaftigkeit des Schlüssels geht), dann können Sie das kurze und süße verwenden

@hash{@key} = ();
15

Das habe ich immer gedacht 

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

war zumindest nett und lesbar/wartbar.

7
Keith

Hier besteht die Voraussetzung, dass der effizienteste Weg, eine Menge von "Enthält das Array X?" Bei checks wird das Array in einen Hash konvertiert. Die Effizienz hängt von der knappen Ressource ab, häufig von der Zeit, manchmal aber auch vom Platzbedarf und manchmal vom Programmieraufwand. Sie verdoppeln mindestens den verbrauchten Speicher, indem Sie eine Liste und einen Hash der Liste gleichzeitig führen. Außerdem schreiben Sie mehr Originalcode, den Sie testen, dokumentieren usw. müssen.

Sehen Sie sich alternativ das Modul List :: MoreUtils an, insbesondere die Funktionen any(), none(), true() und false(). Sie alle nehmen einen Block als Bedingung und eine Liste als Argument, ähnlich wie map() und grep():

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

Ich habe einen kurzen Test durchgeführt, indem ich die Hälfte von/usr/share/dict/words in ein Array geladen habe (25000 Wörter) und dann unter Verwendung beider Arrays nach elf Wörtern gesucht habe, die aus dem gesamten Wörterbuch (alle 5000 Wörter) im Array ausgewählt wurden -to-Hash-Methode und die Funktion any() aus List :: MoreUtils.

Unter Perl 5.8.8, das aus dem Quellcode erstellt wurde, läuft die Array-to-Hash-Methode fast 1100x schneller als die Methode any() (1300x schneller unter Ubuntu 6.06-Paket Perl 5.8.7).

Das ist jedoch nicht die ganze Geschichte - die Konvertierung von Array zu Hash dauert ungefähr 0,04 Sekunden, was in diesem Fall die Zeiteffizienz der Array-zu-Hash-Methode um das 1,5-fache schneller macht als die Methode any(). Immer noch gut, aber nicht annähernd so hervorragend.

Mein Bauchgefühl ist, dass die Array-to-Hash-Methode in den meisten Fällen any() schlagen wird, aber ich würde mich viel besser fühlen, wenn ich mehr solide Metriken hätte (viele Testfälle, anständige statistische Analysen, vielleicht einige große -O algorithmische Analyse jeder Methode usw.) Je nach Ihren Anforderungen ist List :: MoreUtils eine bessere Lösung. Es ist sicherlich flexibler und erfordert weniger Programmierung. Denken Sie daran, vorzeitige Optimierung ist eine Sünde ... :)

7
arclight

In Perl 5.10 gibt es den nahezu magischen ~~-Operator:

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

Siehe hier: http://dev.Perl.org/Perl5/news/2007/Perl-5.10.0.html

5
RET

Der Vollständigkeit halber noch zu erwähnen, meine übliche Methode, dies mit 2 Arrays gleicher Länge zu tun, @keys und @vals, die Sie bevorzugen würden, wären ein Hash ...

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

5
Tamzin Blake

Sie können auch Perl6 :: Junction verwenden.

use Perl6::Junction qw'any';

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

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

Raldis Lösung kann bis zu diesem Punkt verschärft werden (das '=>' vom Original ist nicht erforderlich):

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

Diese Technik kann auch verwendet werden, um Textlisten in Hashes umzuwandeln:

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

Wenn Sie eine Zeile mit folgenden Werten haben: "foo = 1, bar = 2, baz = 3", können Sie Folgendes tun:

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

[Bearbeiten, um aufzunehmen]


Eine andere angebotene Lösung (die zwei Zeilen dauert) ist:

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

Wenn Sie viele theoretische Operationen ausführen, können Sie auch Set :: Scalar oder ein ähnliches Modul verwenden. Dann wird $s = Set::Scalar->new( @array ) das Set für Sie erstellen - und Sie können es mit: $s->contains($m) abfragen.

1
zby

Sie können den Code in eine Subroutine einfügen, wenn Sie nicht möchten, dass Ihr Namespace verschmutzt wird.

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

Oder noch besser:

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;

Wenn Sie wirklich eine Arrayreferenz übergeben wollten:

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);

gibt (Hinweis: Wiederholte Tasten erhalten den Wert an der höchsten Position im Array - dh 8-> 2 und nicht 6)

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

Sie können auch Tie :: IxHash überprüfen, um geordnete assoziative Arrays zu implementieren. Auf diese Weise können Sie beide Arten von Suchvorgängen (Hash und Index) für eine Kopie Ihrer Daten durchführen.

0
Dave G