2010-10-21 10 views
12

Ho un hash abbastanza grande (alcune chiavi da 10 M) e vorrei eliminare alcuni elementi da esso.Come si eliminano gli elementi hash durante l'iterazione?

Di solito non mi piace usare delete o splice, e finisco per copiare quello che voglio invece di cancellare quello che non faccio. Ma questa volta, dato che l'hash è molto grande, penso che mi piacerebbe cancellarlo direttamente da esso.

così sto facendo qualcosa di simile:

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

e sembra funzionare bene. Ma ... e se mi piacerebbe cancellare alcuni elementi prima ancora di iterarli? Mi spiego con l'esempio:

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... 
} 
} 

ho pensato di alcune possibili soluzioni - come controllare se una chiave esiste ancora come il primo passo per il ciclo o il primo looping e la creazione di un elenco di chiavi da cancellare (senza in realtà l'eliminazione loro), quindi effettivamente cancellando in un altro ciclo.

Cosa ne pensi di questo?

UPDATE

E 'sembra che l'approccio di un passaggio doppio ha un consenso. Tuttavia, è abbastanza inefficiente nel senso che durante il primo passaggio ho ricontrollato le chiavi che erano già state contrassegnate per la cancellazione. Questo è un po 'ricorsivo, perché non solo controllo la chiave, ma calcolerò anche le altre chiavi che dovrebbero essere cancellate, sebbene fossero già calcolate dalla chiave originale.

Forse ho bisogno di usare qualche altra struttura dati dinamica per iterare sui tasti, che verrà aggiornata dinamicamente?

+0

*** "I tasti di doppio controllo che erano già contrassegnati per la cancellazione "*** vedi la mia soluzione per un'alternativa frugale – Borodin

risposta

2

In base all'esempio nella domanda, è possibile utilizzare uno grep per filtrare le chiavi corrispondenti al token $key.

Aggiornamento

Il tuo commento è chiarito il vostro bisogno. Il mio suggerimento sarebbe quello di determinare gli indici che corrispondono al tuo fabbisogno e aggiornarti di conseguenza su @keys. L'idea è di aggiornare @keys durante il looping in modo da evitare inutili iterazioni.

Ho implementato il semplice grep come funzione personalizzabile qui.

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

il mio esempio era semplicistico, ma non è questo il problema - so come trovare le chiavi che devono essere cancellate, sia che usi grep o qualsiasi magia funzione che ottiene una chiave che dovrebbe essere cancellata e restituisce una lista di altre chiavi che dovrebbero essere cancellate. La domanda è come superare bene il fatto che se elimini una chiave prima che il ciclo lo raggiunga, ci arriverò comunque in seguito, anche se non esiste già. Immagino che un semplice 'next a meno che esista ($ hash {$ key})', ma mi chiedevo se ci fossero altri suggerimenti. –

4

ne dite di questo:

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

vi consiglio di fare due passaggi perché è più robusto. L'ordine hash è effettivamente casuale, quindi non ci sono garanzie che vedrai le chiavi "primarie" prima di quelle correlate. Ad esempio, se should_be_deleted() rileva solo le chiavi primarie che non sono ricercate e quelle correlate sono calcolate, si potrebbe finire per elaborare dati indesiderati. Un approccio a due passaggi evita questo problema.

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

È possibile contrassegnare gli elementi di hash da eliminare impostando i loro valori a undef. Ciò evita lo spreco di spazio su un elenco separato di chiavi da eliminare, oltre a evitare i controlli sugli elementi già contrassegnati per la cancellazione.E sarebbe anche meno spreco di utilizzare each invece di for, che crea un elenco di tutte le chiavi di hash prima di iniziare a iterare il ciclo

Ti piace questa

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

Un buon approccio supponendo che 'undef' non sia un valore valido. C'è un intervallo di tempo/memoria nel fare il secondo passaggio sull'hash completo invece di limitarlo alle chiavi che dovrebbero essere cancellate. Si potrebbe ottimizzare leggermente eliminando immediatamente la chiave primaria (è possibile cancellare l'elemento più recente restituito da 'each') in modo che il secondo passaggio fosse più breve. –

Problemi correlati