Perl хеш в скалярном контексте

Иногда при работе с хешами можно увидеть какие-то странные дроби.

Вот пример программы:

#!/usr/bin/perl

my %h = (
    a => 1,
    b => 2,
);

print "hash: " . %h;

Если запустить эту программу на версии Perl 5.22, то вывод этот программы будет текст hash: 2/8.

Что это за дробь?

При использовании оператора точка хеш используется в скалярном контексте. print "hash: " . %h; это то же самое что и print "hash: " . scalar(%h);

Строка 2/8 — это результат работы выражения scalar(%h).

Строка 2/8 рассказывает про внутреннее устройство хеша. Это статистика использования бакетов. Это совершенно внутренняя кухня, которая очень редко нужна. Внутри хеша аллоцируются специальные разделы под названием бакеты, в которых и хранятся данные. То что записано справа от дроби — это количество аллоцированных бакетов. Число слева от дроби — это количество реально используемых бакетов. При добавлении пар значений в хеш в какой-то момент количество аллоцированных бакетов увеличивается, чтобы хеш работал эффективно.

Вот пример программы, которая показывает как изменяется эти цифры при добавлении значений в хеш:

#!/usr/bin/perl

use feature qw(say);

my %h;

foreach my $i (1 .. 17) {
    $h{$i} = $i;
    say "$i - " . scalar(%h);
}

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

1 - 1/8
2 - 2/8
3 - 3/8
4 - 3/8
5 - 4/8
6 - 4/8
7 - 4/8
8 - 5/16
9 - 5/16
10 - 6/16
11 - 6/16
12 - 6/16
13 - 6/16
14 - 7/16
15 - 8/16
16 - 12/32
17 - 12/32

Интересно что эти цифры непостоянны. Если несколько раз запускать этот Perl скрипт, то вывод будет немного различаться.

Изменение работы начиная с Perl 5.26

Внутренняя статистика хеша нужна исключительно редко. Поэтому начиная с версии Perl 5.26 поведение хеша в скалярном контексте было изменено. Теперь хеш в скалярном контексте возвращает число — количество пар элементов в хеше. И, честно говоря, именно это все и ожидают получить обращаясь к хешу в скалярном контексте.

До версии Perl 5.26 для того чтобы получить количество пар элементов в хеше нужно было написать scalar(keys(%h));. Начиная с версии Perl 5.26 теперь это же число можно получить с помощью scalar(%h);.

Но если кому-то нужно получить именно дробь со статистикой по хешу, то это возможно сделать с помощью функции bucket_ratio из библиотеки Hash::Util. Вот пример программы, которая на версии Perl 5.26 выводит текст hash: 2/8.

#!/usr/bin/perl

use Hash::Util qw(bucket_ratio);

my %h = (
    a => 1,
    b => 2,
);

print "hash: " . bucket_ratio(%h);

Дополнительная статистика по хешу

Если же все-так нужно понять что происходит с хешом, то можно использовать и другие инструменты. Начиная с версии Perl 5.22 в библиотеке Hash::Util появилась функция bucket_stats_formatted, вот пример ее использования:

#!/usr/bin/perl

use Hash::Util qw(bucket_stats_formatted);

my %h = (
    a => 1,
    b => 2,
);

print bucket_stats_formatted(\%h);

Вывод этой программы:

Keys: 2 Buckets: 2/8 Quality-Score: 0.94 (Good)
Utilized Buckets: 25.00% Optimal: 25.00% Keys In Collision: 0.00%
Chain Length - mean: 1.00 stddev: 0.00
Buckets              8 [00000011]
Len   0  75.00%      6 [######]
Len   1  25.00%      2 [##]
Keys                 2 [11]
Pos   1 100.00%      2 [##]

Другие статьи

Комментарии