Есть ли простой способ проверить существование и определение хеша хеш-элемента?

Мне нужно проверить хэш Perl хеш-элемента, такого как $Table{$key1}{$key2}, чтобы он существовал и был определен. Вот что я делаю. (Я понятия не имею, что $key1 вообще существует)

if 
((defined $Table{$key1}) &&
 (exists  $Table{$key1}) &&
 (defined $Table{$key1}{$key2}) &&
 (exists  $Table{$key1}{$key2})) 
{
   #do whatever
}

Есть ли более простой и чистый способ сделать это?


person WL.    schedule 27.04.2010    source источник
comment
Пожалуйста, отредактируйте свой вопрос, чтобы использовать блоки кода для вашего кода.   -  person justkt    schedule 27.04.2010


Ответы (5)


Вам не нужно проверять каждый уровень иерархии: вы можете просто перейти к интересующему вас значению. exists не проверяет определенность, только если слот в хэше существует (он может существовать с неопределенным значением), поэтому, если вам важно, чтобы значение было определено, вам нужно будет вызвать defined, а не существует. Если значение не определено, оно оценивается в логическом контексте как false, поэтому мы можем ввести немного меньше и сократить ваш пример до:

if ($Table{$key1}{$key2})
{
   # do whatever
}

Однако, если значение в этом ключе определено, но является «ложным» (численно оценивается как ноль или является пустой строкой), это может привести к ложному отрицательному результату, поэтому мы должны явно проверить определенность, если это возможно:

if (defined $Table{$key1}{$key2})
{
   # do whatever
}

Если вы не хотите автоматически оживлять $Table{$key1}, вы можете сначала проверить его существование, что приводит нас к "лучшему" способу для общего случая:

if (exists $Table{$key1} and defined $Table{$key1}{$key2})
{
   # do whatever
}

Если вы собираетесь делать это много для различных полей в хэше, вы можете добавить некоторые методы доступа в стиле OO, которые сделают эту работу за вас:

sub has_field
{
    my ($this, $fieldName) = @_;
    return exists $this->{data} && defined $this->{data}{$fieldName});
}

Я уверен, что вы уже читали его, но не помешает еще раз прочитать соответствующую документацию:

Учитывая выражение, задающее элемент хеша или элемент массива, exists возвращает значение true, если указанный элемент в хеше или массиве когда-либо был инициализирован, даже если соответствующее значение не определено. Элемент не оживляется автоматически, если он не существует.
...
Элемент хеша или массива может быть истинным, только если он определен, и определен, если он существует, но обратное не обязательно верно.

person Ether    schedule 27.04.2010
comment
это всегда будет автооживляться $Table{$key1}, если оно не существует - person Eric Strom; 27.04.2010
comment
Я бы сделал неправильный код более заметным, оставив вокруг него всевозможные комментарии со словами: «Не делайте этого!!!!!!!». - person brian d foy; 27.04.2010
comment
@brian: я не знаю, назвал бы я более ранние формы неправильным кодом; в некоторых обстоятельствах достаточно просто проверить поле на достоверность (например, если автовивация не имеет значения или не является фактором). - person Ether; 27.04.2010
comment
Непреднамеренная автовивификация — это неправильно. Не игнорируйте это и не создавайте плохие привычки, которые потом будут кусать людей. - person brian d foy; 27.04.2010
comment
Автовивификация не имеет значения ... до тех пор, пока это не произойдет, а затем это PITA для отладки того, откуда взялись фиктивные данные (которые искажают каждый раз, когда вы перебираете ключи/значения) в вашем хэше. - person Michael Carman; 27.04.2010

Следующее короче и защитит от автовивификации:

 if (exists $table{$key1} and defined $table{$key1}{$key2}) {...}

Другие проверки в вашем коде не нужны.

person Eric Strom    schedule 27.04.2010

Сначала проверьте существование, затем определенность. (Значение может существовать без определения, но не может быть определено без существования.) Вы должны протестировать промежуточные уровни с помощью exists, чтобы предотвратить непреднамеренное автооживление. Для последнего уровня вам нужно только позвонить defined. Когда слоев не слишком много, кодировать напрямую несложно:

if (exists $hash{a} && defined $hash{a}{b}) {...}

Это становится неудобным, если есть много слоев:

if (exists $hash{a} && exists $hash{a}{b} && exists $hash{a}{b}{c} ...) {...}

В этом случае вы можете написать версию defined, которая не автоматически оживляет промежуточные значения:

sub safe_defined {
    my $h = shift;

    foreach my $k (@_) {
        if (ref $h eq ref {}) {
            return unless exists $h->{$k};
            $h = $h->{$k};
        }
        else {
            return;
        }
    }

    return defined $h;
}

Вы используете его следующим образом:

if (safe_defined(\%hash, qw(a b c))) {
     say $hash{a}{b}{c};
}

Примечание. Эта версия функции ограничена.

  • Он обрабатывает только вложенные хэши. Perl позволяет вам создавать произвольные структуры данных, такие как хэш массивов скалярных ссылок...
  • Он не поддерживает благословенные ссылки (т.е. объекты).

По-настоящему общая версия оставлена ​​читателю в качестве упражнения. ;)

person Michael Carman    schedule 27.04.2010

Вы можете проверить Data::Diver. Он погружается в структуры данных без автооживления. Синтаксис будет таким:

if ( defined Dive(\%Table, $key1, $key2) ) { ... }

или даже:

if ( defined(my $value = Dive(\%Table, $key1, $key2) ) ) {
  ...do something with $value...
}
person runrig    schedule 27.04.2010

Здорово! Спасибо вам всем за ответ.

Поскольку автооживление является проблемой для меня, в настоящее время я использую неудобный подход, т.е. если (существует $Table{$key1} && определено $Table{$key1}{$key2}) {

Делай что угодно

}

Это работает для меня, однако, как вы, ребята, сказали, у меня есть 3-4 уровня вложенного хэша, код немного беспорядочный.

Я проверю Data:Diver. Тот выглядит красивее.

Еще раз спасибо,

person WL.    schedule 28.04.2010