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

80

Nehmen wir an, ich habe ein Array und ich 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 umzuwandeln, in dem die Schlüssel die Elemente des Arrays sind, und dann können Sie einfach sagen

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

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

Raldi
quelle

Antworten:

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

Es ist nicht so kurz wie die "@hash {@array} = ..." - Lösungen, aber für diese muss der Hash und das Array bereits an einer anderen Stelle definiert sein, während dieses ein anonymes Array verwenden und einen anonymen Hash zurückgeben kann.

Dies nimmt jedes Element im Array und koppelt es mit einer "1". Wenn diese Liste von Paaren (Schlüssel, 1, Schlüssel, 1, Schlüssel 1) einem Hash zugewiesen wird, werden die ungeraden zu den Schlüsseln des Hash und die geraden zu den jeweiligen Werten.

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

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

Aus den Dokumenten :

Wenn Sie sich nicht sicher sind, warum Sie dort ein '@' für ein Hash-Slice anstelle eines '%' verwenden, denken Sie daran. Die Art der Klammer (quadratisch oder lockig) bestimmt, ob ein Array oder ein Hash betrachtet wird. Andererseits zeigt das führende Symbol ('$' oder '@') im Array oder Hash an, ob Sie einen Singularwert (einen Skalar) oder einen Pluralwert (eine Liste) zurückerhalten.

moritz
quelle
1
Wow, ich habe noch nie davon gehört (oder daran gedacht). Vielen Dank! Ich habe Probleme zu verstehen, wie es funktioniert. Können Sie eine Erklärung hinzufügen? Wie können Sie insbesondere einen Hash mit dem Namen% hash nehmen und mit einem @ -Zeichen darauf verweisen?
Raldi
2
raldi: Es ist ein Hash-Slice, eine Liste von Werten aus dem Hash, also bekommt es die Liste-y @ vor. Siehe perldoc.perl.org/perldata.html#Slices - insbesondere den letzten Absatz des Abschnitts
Uhr
Sie sollten das zu Ihrer Antwort hinzufügen!
Raldi
Könnten Sie auch die RHS erklären? Vielen Dank.
Susheel Javadi
1
(Liste) x $ number repliziert die Liste $ number mal. Die Verwendung eines Arrays im skalaren Kontext gibt die Anzahl der Elemente zurück. (1) x @array ist eine Liste von Einsen mit der gleichen Länge wie @array.
Moritz
39
@hash{@keys} = undef;

Die Syntax hier, in der Sie sich mit einem auf den Hash 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 von =, ein l-Wert, und wir weisen dieser Liste zu, die tatsächlich in den Hash eingeht und die Werte für alle benannten Schlüssel festlegt. In diesem Fall habe ich nur einen Wert angegeben, damit dieser Wert $hash{$keys[0]}eingegeben wird, und die anderen Hash-Einträge werden alle automatisch mit undefinierten Werten belebt (zum Leben erweckt). [Mein ursprünglicher Vorschlag hier war, den Ausdruck = 1 zu setzen, wodurch dieser eine Schlüssel auf 1 und der andere auf 1 gesetzt worden wäre undef. 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 l-Wert, der Ausdruck auf der linken Seite des =, eine Liste ist, die aus dem Hash erstellt wurde, macht es Sinn, warum wir das 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 zuweise; Es ist nur die Existenz der Schlüssel. Was Sie also tun möchten, 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, nur eine existsPrüfung durchzuführen, als sich um den Wert im Hash zu kümmern, obwohl für mich hier nur das Konzept wichtig ist, dass Sie eine Menge nur mit den Schlüsseln des Hash darstellen. Außerdem hat jemand darauf hingewiesen, dass undefwir durch die Verwendung als Wert hier weniger Speicherplatz verbrauchen, als wenn wir einen Wert zuweisen würden. (Und außerdem weniger Verwirrung stiften, da der Wert keine Rolle spielt und meine Lösung nur dem ersten Element im Hash einen Wert zuweisen und die anderen undefbelassen würde. Einige andere Lösungen drehen Wagenräder, um eine Reihe von Werten zu erstellen, in die sie gehen sollen der Hash; völlig verschwendete Mühe).

Skiphoppy
quelle
1
Dieser ist dem anderen vorzuziehen, da keine temporäre Liste zum Initialisieren des Hash erstellt wird. Dies sollte schneller sein und weniger Speicher verbrauchen.
Leon Timmermans
1
Frosty: Sie müssen zuerst "my% hash" deklarieren und dann "@hash {@arr} = 1" (kein "my") ausführen.
Michael Carman
8
= (), nicht = undefnur aus Gründen der Konsistenz bei der impliziten Verwendung von undef für alle Werte, nicht nur für alle nach dem ersten. (Wie in diesen Kommentaren gezeigt, ist es zu einfach, das zu sehen undefund zu denken, dass es einfach in 1 geändert werden kann und alle Hash-Werte beeinflusst.)
ysth
2
Da die Werte hier als "undef" enden (und wahrscheinlich nicht ganz aus dem Grund, den Sie denken - wie ysth betont hat), können Sie den Hash nicht einfach in Code wie "if ($ hash {$ value})" verwenden. Sie benötigen "if (existiert $ hash {$ value})".
Dave Cross
2
Es wäre schön, wenn Sie Ihre Antwort bearbeiten würden, um darauf hinzuweisen, dass sie mit exist verwendet werden muss, dass exist effizienter ist als die Überprüfung der Wahrhaftigkeit durch Laden des Hash-Werts und dass undef weniger Platz benötigt als 1.
bhollis
16

Beachten Sie, dass if ( exists $hash{ key } )Sie das Kurze und Süße verwenden können , wenn das Tippen für Sie nicht zu viel Arbeit ist (was ich lieber verwende, da es wirklich um das Vorhandensein eines Schlüssels als um die Wahrhaftigkeit seines Werts geht)

@hash{@key} = ();
Aristoteles Pagaltzis
quelle
8

Das habe ich immer gedacht

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

war zumindest nett und lesbar / wartbar.

Keith
quelle
7

Hier besteht die Voraussetzung, dass der effizienteste Weg ist, viele "Enthält das Array X?" Bei Checks wird das Array in einen Hash konvertiert. Die Effizienz hängt von der knappen Ressource ab, oft von Zeit, manchmal von Platz und manchmal von Programmieraufwand. Sie verdoppeln mindestens den Speicherbedarf, indem Sie gleichzeitig eine Liste und einen Hash der Liste führen. Außerdem schreiben Sie mehr Originalcode, den Sie testen, dokumentieren usw. müssen.

Als Alternative Blick auf die Liste :: MoreUtils Modul, speziell die Funktionen any(), none(), true()und false(). Sie alle nehmen einen Block als Bedingung und eine Liste als Argument, ähnlich map()und grep():

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

Ich führte einen Schnelltest durch, lud die Hälfte von / usr / share / dict / words in ein Array (25000 Wörter) und suchte dann nach elf Wörtern, die aus dem gesamten Wörterbuch (alle 5000. Wörter) im Array ausgewählt wurden, wobei beide Arrays verwendet wurden -to-Hash-Methode und die any()Funktion von List :: MoreUtils.

Unter Perl 5.8.8, das aus dem Quellcode erstellt wurde, wird die Array-to-Hash-Methode fast 1100-mal schneller ausgeführt als die any()Methode (1300-mal schneller unter Ubuntu 6.06s Perl 5.8.7).

Dies ist jedoch nicht die ganze Geschichte - die Umwandlung von Array in Hash dauert etwa 0,04 Sekunden, was in diesem Fall die Zeiteffizienz der Array-zu-Hash-Methode auf 1,5x-2x schneller als die any()Methode verringert . Immer noch gut, aber bei weitem nicht so herausragend.

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

Arclight
quelle
Dies beantwortet die Frage nicht. Es fehlt auch der Punkt ... Die Konvertierung von Array in Hash erfolgt nur einmal ... insgesamt 0,04 Sekunden (im Jahr 2008) werden zur Laufzeit des Programms hinzugefügt, während Suchvorgänge häufig ausgeführt werden.
Jim Balter
2
Ich habe versucht, das zugrunde liegende Problem zu lösen und nicht nur die Frage zu beantworten. List::MoreUtilskann je nach Anwendungsfall eine geeignete Methode sein oder auch nicht. Ihr Anwendungsfall kann viele Suchvorgänge enthalten. andere vielleicht nicht. Der Punkt ist, dass sowohl die Umwandlung von Array in Hash als auch List::MoreUtilsdas zugrunde liegende Problem der Bestimmung der Mitgliedschaft gelöst werden; Wenn Sie mehrere Ansätze kennen, können Sie die beste Methode für Ihren speziellen Anwendungsfall auswählen.
Arclight
5

In Perl 5.10 gibt es den 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

RET
quelle
1
Wenn Sie dies für ein großes Array mehrmals tun, ist dies möglicherweise viel langsamer.
ysth
1
Es war der "Smart Match Operator" :)
Brian D Foy
5

Der Vollständigkeit halber auch erwähnenswert, meine übliche Methode, dies mit 2 Arrays gleicher Länge zu tun, @keysund @valsdie Sie bevorzugen würden, war ein Hash ...

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

Tamzin Blake
quelle
4
Die übliche Redewendung für @keys-1ist $#keys.
Stefan Majewsky
@StefanMajewsky Ich habe das schon eine Weile nicht mehr gesehen. Ich halte mich selbst davon fern - es ist hässlich.
Tamzin Blake
3

Raldis Lösung kann bis dahin 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 Wertezeile wie die folgende haben: "foo = 1, bar = 2, baz = 3", können Sie Folgendes tun:

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

[BEARBEITEN, um einzuschließen]


Eine andere angebotene Lösung (die zwei Zeilen umfasst) 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;
Eisig
quelle
1
Der Unterschied zwischen $ _ => 1 und $ _, 1 ist rein stilistisch. Persönlich bevorzuge ich =>, da es den Schlüssel / Wert-Link expliziter anzuzeigen scheint. Ihre @hash {@array} = 1-Lösung funktioniert nicht. Nur einer der Werte (der mit dem ersten Schlüssel in @array verknüpfte) wird auf 1 gesetzt.
Dave Cross
2

Sie können auch Perl6 :: Junction verwenden .

use Perl6::Junction qw'any';

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

if( any(@arr) == 1 ){ ... }
Brad Gilbert
quelle
1
Wenn Sie dies für ein großes Array mehrmals tun, ist dies möglicherweise viel langsamer.
ysth
1
Eigentlich ist es viel langsamer, es einmal zu machen. es muss ein Objekt erstellen. Kurz darauf wird das Objekt zerstört. Dies ist nur ein Beispiel dafür, was möglich ist.
Brad Gilbert
1

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

zby
quelle
1

Sie können den Code in eine Unterroutine einfügen, wenn Sie Ihren Namespace nicht verschmutzen möchten.

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 Array-Referenz übergeben möchten:

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

my @key_list = qw'one two three';
my $hash_ref = keylist @key_list;
Brad Gilbert
quelle
%hash = map{ $_, undef } @keylist
Brad Gilbert
1
#!/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 (beachten Sie, dass wiederholte Tasten den Wert an der größten Position im Array erhalten - dh 8-> 2 und nicht 6)

$VAR1 = {
          '8' => '2',
          '4' => '3',
          '9' => '1',
          '2' => '5',
          '5' => '4'
        };
Mark Dibley
quelle
Ein Hasref scheint hier mehr als ein wenig übertrieben zu sein.
Bobbogo
0

Vielleicht möchten Sie auch Tie :: IxHash ausprobieren , das geordnete assoziative Arrays implementiert. Auf diese Weise können Sie beide Arten von Suchvorgängen (Hash und Index) für eine Kopie Ihrer Daten durchführen.


quelle