it-swarm-eu.dev

Jak v Perlu vytvořím haš, jejíž klíče pocházejí z daného pole?

Řekněme, že mám pole a vím, že budu dělat spoustu "Obsahuje pole X?" kontroly. Efektivní způsob, jak toho dosáhnout, je přeměnit toto pole na haš, kde klíče jsou prvky pole, a pak stačí říct 

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

Existuje snadný způsob, jak provést tuto konverzi pole na hash? V ideálním případě by měla být dostatečně univerzální, aby se anonymní pole a vrátit anonymní hash.

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

Není to tak krátké jako řešení "@hash {@array} = ...", ale ty vyžadují, aby hash a pole již byly definovány někde jinde, zatímco toto může mít anonymní pole a vrátit anonymní hash.

Co to dělá, je vzít každý prvek v poli a spárovat jej s "1". Když se tento seznam párů (klíč, 1, klíč, 1, klíč 1) přiřadí k hash, liché číslice se stanou klávesami hash a sudé číslice se stanou příslušnými hodnotami.

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

Je to hash řez, seznam hodnot z hash, takže dostane seznam-y @ vpředu.

Z dokumenty :

Pokud jste zmatení, proč používáte '' 'Tam na hašovacím řezu místo '% ', Tak si to rozmyslete. Typ závorky (Čtvercová nebo kudrnatá) Určuje, zda se jedná o pole nebo znak . Na druhé straně, Ruka, vedoucí symbol ('$' nebo '@') Na poli nebo hash ukáže zda Vy dostanete zpět singulární hodnotu . ] (skalární) nebo množné číslo (seznam).

41
moritz
@hash{@keys} = undef;

Syntaxe zde, kde odkazujete na hash s @, je hash řez. V podstatě říkáme $hash{$keys[0]} AND $hash{$keys[1]} AND $hash{$keys[2]} ... je seznam na levé straně =, lvalue, a přidělujeme se tomuto seznamu, který skutečně jde do hash a nastavuje hodnoty pro všechny pojmenované klíče. V tomto případě jsem zadal pouze jednu hodnotu, takže hodnota přejde do $hash{$keys[0]} a ostatní položky hash všechny auto-vivify (přijdou k životu) s nedefinovanými hodnotami. [Můj původní návrh zde byl nastaven výraz = 1, který by nastavil jeden klíč na 1 a ostatní na undef. Změnil jsem ji kvůli konzistenci, ale jak uvidíme níže, přesné hodnoty na tom nezáleží.]

Když si uvědomíte, že hodnota, výraz na levé straně =, je seznam vytvořený z hash, pak to začne dělat smysl, proč používáme @. [Až na to, že se to změní v Perlu 6.]

Myšlenkou je, že používáte hash jako sadu. Nezáleží na tom, jakou hodnotu přiděluji; je to jen existence klíčů. Takže to, co chcete dělat, není něco jako:

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

místo toho:

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

Je to vlastně efektivnější spustit kontrolu exists, než se obtěžovat hodnotou v hašování, i když pro mě je důležitá právě zde představa, že reprezentujete množinu právě s klíči hash. Někdo poukázal na to, že použitím undef jako hodnoty zde budeme spotřebovávat méně úložného prostoru, než bychom přiřadili hodnotu. (A také generovat méně zmatku, protože hodnota nezáleží, a mé řešení by přiřadilo hodnotu pouze prvnímu prvku v haši a nechalo ostatní undef, a některá další řešení otáčejí kartičky, aby vytvořily řadu hodnot, které mají jít. do hash, zcela zbytečné úsilí).

34
skiphoppy

Nezapomeňte, že pokud zadáte if ( exists $hash{ key } ) příliš mnoho práce pro vás (což raději používám, protože záležitostí je skutečně přítomnost klíče, nikoli pravdivost jeho hodnoty), můžete použít krátký a sladký

@hash{@key} = ();
15

Vždycky jsem si to myslel 

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

byl alespoň pěkný a čitelný/udržovatelný.

7
Keith

Tam je předpoklad, že nejúčinnější způsob, jak udělat hodně "Má pole obsahuje X?" kontroly je převést pole na hash. Efektivita závisí na omezeném zdroji, často časovém, ale někdy i prostorovém a někdy programátorském úsilí. Jste alespoň zdvojnásobení paměti spotřebované tím, že drží seznam a hash seznamu kolem současně. Navíc píšete více originálního kódu, který budete muset otestovat, dokumentovat atd.

Alternativně se podívejte na modul List :: MoreUtils, konkrétně na funkce any(), none(), true() a false(). Všichni berou blok jako podmíněný a seznam jako argument, podobný map() a grep():

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

Udělal jsem rychlý test, načítání do poloviny/usr/share/dict/words do pole (25000 slov), pak jsem hledal jedenáct slov vybraných z celého slovníku (každé 5000th Word) v poli, pomocí pole -to-hash a funkce any() z List :: MoreUtils.

Na Perlu 5.8.8 postaveném ze zdroje běží metoda array-to-hash téměř 1100x rychleji než metoda any() (1300x rychlejší v balíku Perl 5.8.7 s Ubuntu 6.06).

To však není celý příběh - konverze pole na hash trvá asi 0,04 sekundy, což v tomto případě zabíjí časovou efektivitu metody array-to-hash na 1,5x-2x rychlejší než metoda any(). Stále dobrá, ale ne tak hvězdná.

Můj pocit střeva je ten, že metoda array-to-hash ve většině případů porazí any(), ale já bych se cítil mnohem lépe, kdybych měl nějaké další solidní metriky (spousta testovacích případů, slušné statistické analýzy, možná nějaké velké -O algoritmická analýza každé metody, atd.) V závislosti na vašich potřebách, List :: MoreUtils může být lepším řešením; je to jistě flexibilnější a vyžaduje méně kódování. Pamatujte si, že předčasná optimalizace je hřích ... :)

7
arclight

V prostředí Perl 5.10 existuje operátor s téměř magií.

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

Viz zde: http://dev.Perl.org/Perl5/news/2007/Perl-5.10.0.html

5
RET

Můžete také použít Perl6 :: Junction .

use Perl6::Junction qw'any';

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

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

Také stojí za zmínku pro úplnost, můj obvyklý způsob, jak to udělat s 2 stejná délka pole @keys a @vals, které byste raději byly hash ...

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

2
Tamzin Blake

Raldiho řešení může být k tomuto zpřísněno ('=>' z originálu není nutné):

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

Tato technika může být také použita pro soustružení textových seznamů na hash:

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

Navíc pokud máte řádek hodnot takto: "foo = 1, bar = 2, baz = 3", můžete to provést:

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

[EDIT zahrnout]


Další nabízené řešení (které má dvě řady) je:

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

Pokud provádíte mnoho nastavených teoretických operací - můžete také použít Set :: Scalar nebo podobný modul. Pak $s = Set::Scalar->new( @array ) vytvoří sestavu pro vás - a můžete se na ni zeptat: $s->contains($m).

1
zby

Kód můžete umístit do podprogramu, pokud nechcete znečišťovat jmenný prostor.

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

Nebo ještě lepší:

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;

Pokud opravdu chcete předat odkaz na pole:

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

dává (všimněte si, že opakované klávesy dostanou hodnotu na největší pozici v poli - tj. 8-> 2 a ne 6)

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

Můžete také vyzkoušet Tie :: IxHash , která implementuje uspořádaná asociativní pole. To by umožnilo provádět oba typy vyhledávání (hash a index) na jedné kopii dat.

0
Dave G