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.