Jak w Perlu utworzyć skrót, którego klucze pochodzą z podanej tablicy?


80

Powiedzmy, że mam tablicę i wiem, że będę robił dużo pytań „Czy tablica zawiera X?” czeki. Efektywnym sposobem na to jest przekształcenie tej tablicy w skrót, w którym klucze są elementami tablicy, a następnie możesz po prostu powiedzieć

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

Czy istnieje łatwy sposób na wykonanie tej konwersji tablicy na skrót? W idealnym przypadku powinien być wystarczająco wszechstronny, aby wziąć anonimową tablicę i zwrócić anonimowy skrót.

Odpowiedzi:


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

Nie jest tak krótkie, jak rozwiązania "@hash {@array} = ...", ale te wymagają, aby hash i tablica były już zdefiniowane w innym miejscu, podczas gdy ten może wziąć anonimową tablicę i zwrócić anonimowy hash.

To, co robi, to pobranie każdego elementu tablicy i powiązanie go z „1”. Gdy ta lista par (klucz, 1, klucz, 1, klucz 1) zostanie przypisana do skrótu, nieparzyste stają się kluczami skrótu, a parzyste stają się odpowiednimi wartościami.


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

Jest to wycinek z krzyżykiem, lista wartości z skrótu, więc na początku otrzymuje list-y @.

Z dokumentów :

Jeśli nie wiesz, dlaczego używasz znaku „@” zamiast „%”, pomyśl o tym w ten sposób. Rodzaj nawiasu (kwadratowy lub zawiasowy) decyduje o tym, czy jest to tablica, czy hash. Z drugiej strony wiodący symbol („$” lub „@”) w tablicy lub hashu wskazuje, czy otrzymujesz wartość pojedynczą (skalarną), czy mnogą (lista).


1
Wow, nigdy o tym nie słyszałem (ani o nim nie myślałem). Dzięki! Mam problem ze zrozumieniem, jak to działa. Czy możesz dodać wyjaśnienie? W szczególności, jak możesz wziąć hash o nazwie% hash i odnieść się do niego za pomocą znaku @?
raldi

2
raldi: to wycinek z krzyżykiem, lista wartości z krzyżyka, więc na początku otrzymuje list-y @. Zobacz perldoc.perl.org/perldata.html#Slices - szczególnie ostatni akapit sekcji
ysth

Powinieneś to dodać do swojej odpowiedzi!
raldi

Czy mógłbyś również wyjaśnić RHS? Dzięki.
Susheel Javadi

1
(lista) x $ number powiela listę $ liczbę razy. Użycie tablicy w kontekście skalarnym zwraca liczbę elementów, więc (1) x @ tablica jest listą jedynek o tej samej długości co @ tablica.
moritz

39
@hash{@keys} = undef;

Składnia tutaj, w której odnosisz się do skrótu za @pomocą skrótu, to wycinek z krzyżykiem. Zasadniczo mówimy $hash{$keys[0]}AND $hash{$keys[1]}AND $hash{$keys[2]}... to lista po lewej stronie =, wartość l, i przypisujemy ją do tej listy, która w rzeczywistości trafia do skrótu i ​​ustawia wartości dla wszystkich nazwanych kluczy. W tym przypadku $hash{$keys[0]}podałem tylko jedną wartość, więc wartość trafia do , a pozostałe wpisy hash wszystkie automatycznie ożywają (ożywają) z niezdefiniowanymi wartościami. [Moją pierwotną sugestią było ustawienie wyrażenia = 1, które ustawiłoby ten jeden klawisz na 1, a pozostałe na undef. Zmieniłem to dla spójności, ale jak zobaczymy poniżej, dokładne wartości nie mają znaczenia.]

Kiedy zdasz sobie sprawę, że lwartość, wyrażenie po lewej stronie =, jest listą zbudowaną z hasha, wtedy zacznie mieć sens, dlaczego tego używamy @. [Chyba że to się zmieni w Perlu 6.]

Chodzi o to, że używasz skrótu jako zestawu. Nie liczy się wartość, którą przypisuję; to tylko istnienie kluczy. Więc to, co chcesz zrobić, to nie coś takiego:

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

zamiast:

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

W rzeczywistości bardziej wydajne jest po prostu uruchomienie existsczeku niż zawracanie głowy wartością w skrócie, chociaż dla mnie ważną rzeczą jest tutaj po prostu koncepcja, że ​​reprezentujesz zestaw tylko za pomocą kluczy skrótu. Ktoś również zwrócił uwagę, że używając undeftutaj wartości jako wartości, zużyjemy mniej miejsca w pamięci, niż przypisując wartość. (A także generuje mniej zamieszania, ponieważ wartość nie ma znaczenia, a moje rozwiązanie przypisałoby wartość tylko do pierwszego elementu w haszu, a pozostawiło pozostałe undef, a niektóre inne rozwiązania obracają koła zębate, aby zbudować tablicę wartości haszysz; całkowicie zmarnowany wysiłek).


1
Ten jest lepszy od drugiego, ponieważ nie tworzy tymczasowej listy do zainicjowania skrótu. Powinno to działać szybciej i zajmować mniej pamięci.
Leon Timmermans

1
Frosty: Najpierw musisz zadeklarować „mój% hash”, a następnie „@hash {@arr} = 1” (bez „my”).
Michael Carman,

8
= (), nie = undeftylko dla spójności w niejawnym używaniu undef dla wszystkich wartości, a nie tylko dla wszystkich po pierwszej. (Jak pokazano w tych komentarzach, zbyt łatwo jest zobaczyć undefi pomyśleć, że można go po prostu zmienić na 1 i wpłynąć na wszystkie wartości skrótu.)
ysth

2
Ponieważ wartości kończą się tutaj jako „undef” (i prawdopodobnie z niezupełnie powodu, o którym myślisz - jak wskazał ysth), nie możesz po prostu użyć skrótu w kodzie, takiego jak „if ($ hash {$ value})”. Potrzebowałbyś „if (istnieje $ hash {$ value})”.
Dave Cross,

2
Byłoby miło, gdybyś zredagował swoją odpowiedź, aby wskazać, że musi być używana z istnieje, że istnieje jest bardziej wydajne niż sprawdzanie prawdziwości przez faktyczne ładowanie wartości skrótu, a undef zajmuje mniej miejsca niż 1.
bhollis

16

Zwróć uwagę, że jeśli pisanie if ( exists $hash{ key } )nie jest dla Ciebie zbyt trudne (co wolę używać, ponieważ interesująca jest tak naprawdę obecność klucza, a nie prawdziwość jego wartości), możesz użyć krótkiego i słodkiego

@hash{@key} = ();

8

Zawsze tak myślałem

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

był przynajmniej ładny i czytelny / możliwy do utrzymania.


7

Istnieje tutaj założenie, że najskuteczniejszy sposób wykonania wielu czynności „Czy tablica zawiera X?” Sprawdzenie polega na przekonwertowaniu tablicy na skrót. Wydajność zależy od rzadkich zasobów, często czasu, ale czasami przestrzeni, a czasami wysiłku programisty. Przynajmniej podwajasz ilość zajętej pamięci, przechowując jednocześnie listę i skrót listy. Poza tym piszesz bardziej oryginalny kod, który będziesz musiał przetestować, udokumentować itp.

Jako alternatywę, spojrzeć na module Lista :: MoreUtils, w szczególności funkcji any(), none(), true()i false(). Wszystkie przyjmują blok jako warunek i listę jako argument, podobnie jak map()i grep():

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

Przeprowadziłem szybki test, ładując połowę / usr / share / dict / words do tablicy (25000 słów), a następnie wyszukując jedenaście słów wybranych z całego słownika (co 5000-te słowo) w tablicy, używając zarówno tablicy -to-hash i any() funkcja z List :: MoreUtils.

W Perlu 5.8.8 zbudowanym ze źródeł metoda tablica do skrótu działa prawie 1100 razy szybciej niż any() metoda (1300 razy szybciej w pakiecie Perl 5.8.7 z Ubuntu 6.06).

Nie jest to jednak pełna historia - konwersja tablicy na skrót zajmuje około 0,04 sekundy, co w tym przypadku zabija wydajność czasową metody tablica do skrótu do 1,5x-2x szybciej niż any()metoda. Wciąż dobry, ale nie tak gwiezdny.

Moje przeczucie jest takie, że metoda tablic-to-hash będzie any()lepsza w większości przypadków, ale czułbym się o wiele lepiej, gdybym miał bardziej solidne metryki (wiele przypadków testowych, przyzwoite analizy statystyczne, może niektóre duże O algorytmiczna analiza każdej metody itp.) W zależności od potrzeb List :: MoreUtils może być lepszym rozwiązaniem; jest z pewnością bardziej elastyczny i wymaga mniej kodowania. Pamiętaj, przedwczesna optymalizacja to grzech ... :)


To nie odpowiada na pytanie. To również mija się z celem ... konwersja tablicy na hash ma miejsce tylko raz ... łącznie 0,04 sekundy (w 2008 r.) Dodane do czasu wykonywania programu, podczas gdy wyszukiwania są wykonywane wiele razy.
Jim Balter

2
Próbowałem rozwiązać podstawowy problem, a nie tylko odpowiedzieć na pytanie. List::MoreUtilsmoże, ale nie musi, być odpowiednią metodą, w zależności od przypadku użycia. Twój przypadek użycia może mieć wiele wyszukiwań; inni mogą nie. Chodzi o to, że zarówno konwersja tablicy do skrótu, jak i List::MoreUtilsrozwiązanie podstawowego problemu określania członkostwa; znajomość wielu podejść pozwala wybrać najlepszą metodę dla konkretnego przypadku użycia.
arclight


5

Warto również zwrócić uwagę na kompletność, moją zwykłą metodę robienia tego z 2 tablicami o tej samej długości @keysi @valsktórą wolałbyś być hashem ...

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


4
Zwykły idiom dla @keys-1to $#keys.
Stefan Majewsky

@StefanMajewsky Dawno nie widziałem tego używanego. Sam trzymam się z dala od tego - jest brzydki.
Tamzin Blake

3

Rozwiązanie Raldiego można zawęzić do tego (znak „=>” z oryginału nie jest konieczny):

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

Ta technika może być również używana do przekształcania list tekstowych w skróty:

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

Dodatkowo, jeśli masz taki wiersz wartości: „foo = 1, bar = 2, baz = 3”, możesz to zrobić:

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

[EDYTUJ, aby uwzględnić]


Innym oferowanym rozwiązaniem (w dwóch liniach) jest:

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;

1
Różnica między $ _ => 1 a $ _, 1 jest czysto stylistyczna. Osobiście wolę =>, ponieważ wydaje się, że bardziej wyraźnie wskazuje link klucz / wartość. Twoje rozwiązanie @hash {@array} = 1 nie działa. Tylko jedna z wartości (ta związana z pierwszym kluczem w @array) zostaje ustawiona na 1.
Dave Cross

2

Możesz także użyć Perl6 :: Junction .

use Perl6::Junction qw'any';

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

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

1
Jeśli robisz to wiele razy dla dużej macierzy, potencjalnie będzie to znacznie wolniejsze.
ysth

1
Właściwie zrobienie tego raz jest dużo wolniejsze. musi stworzyć obiekt. Niedługo potem zniszczy ten obiekt. To tylko przykład tego, co jest możliwe.
Brad Gilbert,

1

Jeśli wykonujesz wiele operacji teoretycznych na zbiorach - możesz również użyć modułu Set :: Scalar lub podobnego. Następnie$s = Set::Scalar->new( @array ) zbuduje zestaw dla Ciebie - i można wyszukać go z: $s->contains($m).


1

Możesz umieścić kod w podprocedurze, jeśli nie chcesz zanieczyszczać swojej przestrzeni nazw.

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

Albo jeszcze lepiej:

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;

Jeśli naprawdę chciałeś przekazać odwołanie do tablicy:

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

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

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

daje (zwróć uwagę, że powtórzone klawisze pobierają wartość z największej pozycji w tablicy - tj. 8-> 2 a nie 6)

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

Hasref wydaje się tutaj bardziej niż trochę przesadzony.
bobbogo

0

Możesz również sprawdzić Tie :: IxHash , który implementuje uporządkowane tablice asocjacyjne. Pozwoliłoby to na wykonanie obu typów wyszukiwania (hash i index) na jednej kopii danych.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.