Что такое 4/16 в хешах? - PullRequest
15 голосов
/ 17 июля 2011
if (%hash){
     print "That was a true value!\n";
}

Это будет верно, если (и только если) хеш имеет хотя бы один ключ-значение пара.

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

Что это за 4/16? Может кто-нибудь показать мне небольшую программу, откуда я вижу, что результат 4/16?

Ответы [ 6 ]

23 голосов
/ 17 июля 2011

С perldoc perldata :

Если вы оцениваете хеш в скалярном контексте, он возвращает false, если хеш пустой. Если есть какие-либо пары ключ / значение, возвращается true; Больше точно, возвращаемое значение является строкой, состоящей из числа использованные сегменты и количество выделенных сегментов, разделенных слэш. Это очень полезно только для того, чтобы выяснить, есть ли у Perl алгоритм внутреннего хэширования плохо работает с вашим набором данных. За Например, вы добавляете 10000 вещей в хеш, но оцениваете% HASH в скалярный контекст показывает «1/16», что означает только один из шестнадцати ведра были затронуты, и, вероятно, содержит все 10000 ваших товар.

Итак, 4/16 будет количество используемых / распределенных интервалов, и что-то вроде следующего будет отображать это значение:

%hash = (1, 2);
print scalar(%hash); #prints 1/8 here
10 голосов
/ 17 июля 2011

Хеш - это массив связанных списков.Функция хеширования преобразует ключ в число, которое используется в качестве индекса элемента массива («корзины»), в который сохраняется значение.Более одного ключа может хэшировать к одному и тому же индексу («столкновение»), ситуация, которая обрабатывается связанными списками.

Знаменатель дроби - это общее количество сегментов.дроби - это число сегментов, в которых есть один или несколько элементов.

Для хэшей с одинаковым количеством элементов, чем больше число, тем лучше.Тот, который возвращает 6/8, имеет меньше коллизий, чем тот, который возвращает 4 / 8.

8 голосов
/ 17 июля 2011

Это слегка измененная версия электронного письма, которое я отправил в список рассылки Perl Beginners, отвечая на этот же вопрос.

Высказывание

my $hash_info = %hash;

Вы получите либо 0 (если хеш пуст), либо соотношение используемых к общее количество ведер Эта информация почти, но не полностью, бесполезен для тебя. Чтобы понять, что это значит, вы должны сначала понять, как работает хеширование.

Давайте реализуем хеш с помощью Perl 5. Первое, что нам нужно, это функция хеширования. Надеемся, что хэширующие функции превращают строки в уникальные номера. Примеры реальных сильных хеш-функций: MD5 или SHA1 , но они имеют тенденцию быть слишком медленными для общего использования, поэтому люди, как правило, используют слабее (то есть те, которые производят менее уникальный результат) функции для хеш-таблиц. Perl 5 использует Боба Дженкинса [по одному] алгоритм, который имеет хороший компромисс между уникальностью и скоростью. За наших Например, я буду использовать очень слабую функцию хеширования:

#!/usr/bin/perl

use strict;
use warnings;

sub weak_hash {
       my $key  = shift;
       my $hash = 1;
       #multiply every character in the string's ASCII/Unicode value together
       for my $character (split //, $key) {
               $hash *= ord $character;
       }
       return $hash;
}

for my $string (qw/cat dog hat/) {
       print "$string hashes to ", weak_hash($string), "\n";
}

Поскольку функции хеширования имеют тенденцию возвращать числа из диапазона, большего, чем мы хотим, обычно вы используете по модулю , чтобы уменьшить диапазон чисел, который он дает назад:

#!/usr/bin/perl

use strict;
use warnings;

sub weak_hash {
       my $key  = shift;
       my $hash = 1;
       #multiply every character in the string's ASCII/Unicode value together
       for my $character (split //, $key) {
               $hash *= ord $character;
       }
       return $hash;
}

for my $string (qw/cat dog hat/) {
       # the % operator is constraining the number
       # weak_hash returns to 0 - 10
       print "$string hashes to ", weak_hash($string) % 11, "\n";
}

Теперь, когда у нас есть функция хеширования, нам нужно где-то сохранить ключ и значение. Это называется хеш-таблицей. Хеш-таблица часто является массив, элементы которого называются сегментами (это сегменты, которые соотношение говорит о). Ведро будет содержать весь ключ / значение пары, которые хэшируют к одному и тому же номеру:

#!/usr/bin/perl

use strict;
use warnings;

sub weak_hash {
       my $key  = shift;
       my $hash = 1;
       for my $character (split //, $key) {
               $hash *= ord $character;
       }
       return $hash;
}

sub create {
       my ($size) = @_;

       my @hash_table;

       #set the size of the array
       $#hash_table = $size - 1;

       return \@hash_table;
}


sub store {
       my ($hash_table, $key, $value) = @_;

       #create an index into $hash_table
       #constrain it to the size of the hash_table
       my $hash_table_size = @$hash_table;
       my $index           = weak_hash($key) % $hash_table_size;

       #push the key/value pair onto the bucket at the index
       push @{$hash_table->[$index]}, {
               key   => $key,
               value => $value
       };

       return $value;
}

sub retrieve {
       my ($hash_table, $key) = @_;

       #create an index into $hash_table
       #constrain it to the size of the hash_table
       my $hash_table_size = @$hash_table;
       my $index           = weak_hash($key) % $hash_table_size;

       #get the bucket for this key/value pair
       my $bucket = $hash_table->[$index];

       #find the key/value pair in the bucket
       for my $pair (@$bucket) {
               return $pair->{value} if $pair->{key} eq $key;
       }

       #if key isn't in the bucket:
       return undef;
}

sub list_keys {
       my ($hash_table) = @_;

       my @keys;

       for my $bucket (@$hash_table) {
               for my $pair (@$bucket) {
                       push @keys, $pair->{key};
               }
       }

       return @keys;
}

sub print_hash_table {
       my ($hash_table) = @_;

       for my $i (0 .. $#$hash_table) {
               print "in bucket $i:\n";
               for my $pair (@{$hash_table->[$i]}) {
                       print "$pair->{key} => $pair->{value}\n";
               }
       }
}

my $hash_table = create(3);

my $i = 0;
for my $key (qw/a b c d g j/) {
       store($hash_table, $key, $i++);
}
print_hash_table($hash_table);

print "the a key holds: ", retrieve($hash_table, "a"), "\n";

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

Чтобы узнать больше о хэшах, задайте здесь вопросы о том, что я сказал, или читайте о них .

4 голосов
/ 09 июля 2015

Фракция - это коэффициент заполнения хеша: использованные сегменты против выделенных сегментов.Также иногда называется коэффициент загрузки .

Чтобы получить «4/16», вам понадобятся некоторые хитрости.4 ключа приведут к 8 ведрам.Таким образом, вам нужно как минимум 9 ключей, а затем удалить 5.

$ perl -le'%h=(0..16); print scalar %h; delete $h{$_} for 0..8; print scalar %h'
9/16
4/16

Обратите внимание, что ваши числа будут различаться, так как начальное число рандомизировано, и вы не сможете предсказать точные столкновения

Скорость заполнения - это важная хэш-информация, когда нужно перефразировать.Perl 5 повторяет со скоростью заполнения 100%, см. Макрос DO_HSPLIT в hv.c.Таким образом, он обменивает память на скорость только для чтения.Нормальная скорость заполнения составляет от 80% до 95%.Вы всегда оставляете дыры, чтобы сохранить некоторые столкновения.Более низкая скорость заполнения приводит к более быстрому доступу (меньше столкновений), но большему количеству повторных попыток.

Вы не сразу видите количество столкновений с дробью.Вам также необходимо keys %hash, чтобы сравнить с числителем дроби число используемых интервалов.

Таким образом, одна часть качества столкновения составляет ключей / использованных интервалов :

my ($used, $max) = split '/',scalar(%hash);
keys %hash / $used;

Но на самом деле вам нужно знать сумму длин всех связанных списков в корзинах.Вы можете получить доступ к этому качеству с помощью Hash::Util::bucket_info

($keys, $buckets, $used, @length_count)= Hash::Util::bucket_info(\%hash)

В то время как доступ к хешу обычно равен O (1), для длинных длин это только O (n / 2), особеннодля длинных ведер.В https://github.com/rurban/perl-hash-stats я предоставляю статистическую информацию о коллизиях для различных хэш-функций для данных набора тестов ядра perl5.Я еще не тестировал компромиссы для разных скоростей заполнения, так как я полностью переписываю текущие хеш-таблицы.

Обновление: для perl5 лучшая скорость заполнения, чем 100%, будет 90%, как тестировалось недавно.Но это зависит от используемой хэш-функции.Я использовал плохую и быструю: FNV1A.С лучшими, более медленными хэш-функциями вы можете использовать более высокую скорость заполнения.Текущее значение по умолчанию OOAT_HARD плохое и медленное, поэтому его следует избегать.

4 голосов
/ 17 июля 2011

Добавление другого ответа, потому что первый уже слишком длинный.

Еще один подход к пониманию того, что означает "4/16", заключается в использовании модуля Hash::Esoteric (код качества предупреждения альфа),Я написал это, чтобы дать мне лучшее представление о том, что происходит внутри хеша, чтобы я мог попытаться понять проблему производительности , которая, кажется, имеет большие хеши.Функция keys_by_bucket из Hash::Esoteric вернет все ключи из хеша, но вместо того, чтобы возвращать их в виде списка, как это делает keys, она возвращает их как AoA, где верхняя частьlevel представляет сегменты, а arrayref внутри него содержит ключи в этом сегменте.

#!/user/bin/env perl

use strict;
use warnings;

use Hash::Esoteric qw/keys_by_bucket/;

my %hash = map { $_ => undef } "a" .. "g";
my $buckets = keys_by_bucket \%hash;

my $used;
for my $i (0 .. $#$buckets) {
    if (@{$buckets->[$i]}) {
        $used++;
    }
    print "bucket $i\n";
    for my $key (@{$buckets->[$i]}) {
        print "\t$key\n";
    }
}

print "scalar %hash: ", scalar %hash, "\n",
      "used/total buckets: $used/", scalar @$buckets, "\n";

Приведенный выше код выводит что-то вроде (фактические данные зависят от версии Perl):

bucket 0
    e
bucket 1
    c
bucket 2
    a
bucket 3
    g
    b
bucket 4
bucket 5
    d
bucket 6
    f
bucket 7
scalar %hash: 6/8
used/total buckets: 6/8
1 голос
/ 17 июля 2011

То, что (%hash) оценивает хеш в скалярном контексте.

Вот пустой хеш:

command_line_prompt> perl -le '%hash=(); print scalar %hash;'

Результат равен 0.

Вот непустой хеш:

command_line_prompt> perl -le '%hash=(foo=>'bar'); print scalar %hash;'

Результатом является строка "1/8".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...