2010-10-21 4 views
12

Mam dość duże hash (niektóre klucze 10M) i chciałbym usunąć niektóre elementy z niego.Jak powinienem usunąć elementy mieszające podczas iteracji?

Zwykle nie lubię korzystać z delete lub splice i kończę kopiowanie tego, co chcę zamiast usuwania tego, czego nie chcę. Ale tym razem, ponieważ hash jest naprawdę duży, myślę, że chciałbym usunąć bezpośrednio z niego.

Więc robię coś takiego:

foreach my $key (keys %hash) { 
if (should_be_deleted($key)) { 
    delete($hash{$key}); 
} 
} 

I wydaje się działać OK. Ale ... co jeśli chciałbym usunąć niektóre elementy, nawet przed ich iteracją? Wytłumaczę na przykładzie:

foreach my $key (keys %hash) { 
if (should_be_deleted($key)) { 
    delete($hash{$key}); 
    # if $key should be deleted, so does "$key.a", "kkk.$key" and some other keys 
    # I already know to calculate. I would like to delete them now... 
} 
} 

myślałem o kilku możliwych rozwiązań - takich jak sprawdzenie, czy klucz istnieje nadal jako pierwszy krok w pętli lub pierwszej pętli i tworząc listę kluczy do usunięcia (bez faktycznie usuwanie ich), a następnie kasowanie w innej pętli.

Co sądzisz o tym?

UPDATE

To wydaje się, że podejście double-pass ma konsensusu. Jest to jednak dość nieefektywne w tym sensie, że podczas pierwszego przejścia podwójnie sprawdzam klucze, które zostały już oznaczone do skasowania. Jest to rekurencyjne, ponieważ nie tylko sprawdzam klucz, ale także obliczam inne klucze, które powinny zostać usunięte, mimo że zostały już obliczone przez oryginalny klucz.

Być może potrzebuję użyć bardziej dynamicznej struktury danych do iteracji kluczy, która będzie aktualizowana dynamicznie?

+0

*** "Ja klucze dokładnie sprawdzić które zostały już oznaczone do skasowania "*** zobacz moje rozwiązanie dla oszczędnej alternatywy – Borodin

Odpowiedz

2

W oparciu o przykładowe pytanie można użyć grep do odfiltrowania kluczy pasujących do Twojego tokena $key.

Aktualizacja

Twój komentarz został sprecyzować swoje potrzeby. Moja sugestia polegałaby na ustaleniu indeksów, które pasują do Twojego wymagania i aktualizacji odpowiednio. Chodzi o aktualizację @keys podczas pętli nad nią, aby uniknąć niepotrzebnych iteracji.

Wprowadziłem tutaj prosty grep jako konfigurowalną funkcję.

sub matches { $_[0] =~ /$_[1]/ ? 1 : 0 } # Simple grep implemented here 

my @keys = keys %hash; # @keys should initially contain all keys 

while (@keys) { 

    my $key = shift @keys; 
    next unless should_be_deleted ($key); # Skip keys that are wanted 

    my @indexes_to_delete = grep { matches ($key, qr/$keys[$_]/) } 0 .. $#keys; 

    delete @hash { @keys[@indexes_to_delete] };  # Remove the unwanted keys 

    splice @keys, $_, 1 foreach @indexes_to_delete; # Removes deleted ... 
                # ... elements from @keys. 
                # Avoids needless iterations. 
} 
+0

mój przykład był prosty, ale to nie jest problem - wiem, jak znaleźć klucze, które należy usunąć, czy to za pomocą grep lub jakiejkolwiek magii funkcja, która otrzymuje klucz, który należy usunąć i zwraca listę innych kluczy, które również powinny zostać usunięte. Pytanie brzmi, jak dobrze przezwyciężyć fakt, że jeśli usunę klucz, zanim pętla go osiągnie, w dalszym ciągu do niego dotrę później, chociaż jeszcze nie istnieje. Domyślam się, że proste 'next until exists ($ hash {$ key})' zrobi, ale zastanawiałem się czy są jakieś inne sugestie. –

4

Jak o tym:

my %to_delete; 

foreach my $key (keys %hash) { 
    if (should_be_deleted($key)) { 
     $to_delete{$key}++; 
    } 
    # add some other keys the same way... 
} 

delete @hash{keys %to_delete}; 
8

polecam robić dwie przepustki, ponieważ jest to bardziej wytrzymałe. Kolejność skrótu jest przypadkowa, więc nie ma żadnych gwarancji, że zobaczysz klucze "podstawowe" przed odpowiednimi. Na przykład, jeśli should_be_deleted() wykrywa tylko klucze podstawowe, które nie są poszukiwane, a powiązane z nimi są obliczane, możesz zakończyć przetwarzanie niechcianych danych. Dwuetapowe podejście pozwala uniknąć tego problemu.

my @unwanted; 
foreach my $key (keys %hash) { 
    if (should_be_deleted($key)) { 
     push @unwanted, $key; 
     # push any related keys onto @unwanted 
    } 
} 

delete @hash{@unwanted}; 

foreach my $key (keys %hash) { 
    # do something 
} 
2

Można oznaczyć elementy mieszające, które mają zostać usunięte, ustawiając ich wartości na undef. Dzięki temu unika się marnowania miejsca na osobnej liście kluczy do usunięcia, a także unikania kontroli elementów już oznaczonych do usunięcia.I byłoby również mniej rozrzutny używać each zamiast for, która buduje listę wszystkich klawiszy skrótu przed rozpoczęciem iteracji pętli

Podoba Ci się to

while (my ($key, $val) = each %hash) { 

    next unless defined $val and should_be_deleted($key); 

    $hash{$key}  = undef; 
    $hash{$key.'a'} = undef; 
    $hash{'kkk'.$key} = undef; 
} 

while (my ($key, $val) = each %hash) { 
    delete $hash{$key} unless defined $val; 
} 
+0

Dobre podejście, zakładając, że 'undef' nie jest poprawną wartością. Istnieje kompromis czasowy/pamięciowy podczas wykonywania drugiego przebiegu nad pełnym hashem, zamiast ograniczania go do kluczy, które powinny zostać usunięte. Możesz to zoptymalizować nieco, natychmiast usuwając klucz podstawowy (można bezpiecznie "usunąć" ostatni element zwracany przez "każdy"), aby drugie przejście było krótsze. –