Подтвердить что ты не робот

Как удалить элементы хэша во время итерации?

У меня довольно большой хеш (некоторые ключи 10M), и я хотел бы удалить из него некоторые элементы.

Мне обычно не нравится использовать delete или splice, и я завершаю копирование того, что хочу, вместо того, чтобы удалять то, что у меня нет. Но на этот раз, так как хэш действительно большой, я думаю, что я хотел бы удалить его прямо из него.

Итак, я делаю что-то вроде этого:

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

И это работает нормально. Но.. что, если я хотел бы удалить некоторые элементы еще до их повторения? Я объясню на примере:

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

Я подумал о некоторых возможных решениях, таких как проверка того, существует ли ключ по-прежнему как первый шаг в цикле или первом цикле и создание списка ключей для удаления (без их удаления), а затем фактически удаляется в другом цикле.

Что вы думаете об этом?

UPDATE

Кажется, что подход с двойным проходом имеет консенсус. Однако он довольно неэффективен в том смысле, что во время первого прохода я дважды проверю ключи, которые уже были отмечены для удаления. Это рекурсивно, потому что не только я проверяю ключ, но и вычисляю другие ключи, которые нужно удалить, хотя они уже были рассчитаны исходным ключом.

Возможно, мне нужно использовать еще одну динамическую структуру данных для итерации по ключам, которая будет динамически обновляться?

4b9b3361

Ответ 1

Я рекомендую сделать два прохода, потому что он более надежный. Порядок хеширования является случайным, поэтому нет никаких гарантий, что вы увидите "первичные" ключи перед соответствующими. Например, если should_be_deleted() обнаруживает первичные ключи, которые не нужны, а соответствующие вычисления рассчитываются, вы можете обработать нежелательные данные. Двухпроходный подход позволяет избежать этой проблемы.

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

Как насчет этого:

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

Ответ 3

На основе примера в вопросе вы можете использовать grep для фильтрации ключей, соответствующих вашему токену $key.

Обновление

Ваш комментарий разъяснил вашу потребность. Мое предложение состояло в том, чтобы определить индексы, которые соответствуют вашему требованию, и обновить соответственно @keys. Идея состоит в том, чтобы обновить @keys во время цикла, чтобы избежать ненужных итераций.

Я реализовал простой grep как настраиваемую функцию здесь.

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

Ответ 4

Вы можете пометить элементы хэша для удаления, установив их значения в undef. Это позволяет избежать пробелов в отдельном списке удаляемых ключей, а также избегать проверки элементов, уже отмеченных для удаления. И было бы менее бесполезно использовать each вместо for, который строит список всех хэш-ключей, прежде чем начинать итерацию цикла

Подобно этому

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