В Perl, как мне создать хеш, ключи которого поступают из данного массива? - PullRequest
75 голосов
/ 18 сентября 2008

Допустим, у меня есть массив, и я знаю, что собираюсь сделать много "Содержит ли массив X?" чеки. Эффективный способ сделать это - превратить этот массив в хеш, где ключами являются элементы массива, а затем вы можете просто сказать

if($hash{X}) { ... }

Есть ли простой способ сделать это преобразование массива в хэш? В идеале он должен быть достаточно универсальным, чтобы брать анонимный массив и возвращать анонимный хеш.

Ответы [ 14 ]

110 голосов
/ 18 сентября 2008
%hash = map { $_ => 1 } @array;

Это не так коротко, как у решений "@hash {@array} = ...", но те требуют, чтобы хэш и массив уже были определены где-то еще, тогда как этот может взять анонимный массив и вернуть анонимный хэш.

Для этого нужно взять каждый элемент в массиве и связать его с «1». Когда этот список пар (ключ, 1, ключ, 1, ключ 1) назначается хешу, нечетные становятся ключами хеша, а четные становятся соответствующими значениями.

41 голосов
/ 18 сентября 2008
 @hash{@array} = (1) x @array;

Это срез хеша, список значений из хеша, поэтому он получает список-y @ впереди.

С Документы :

Если вы не уверены, почему вы используете вместо этого есть знак @ о '%', думай об этом так. тип кронштейна (квадратный или фигурный) определяет, является ли это массивом или хэш просматривается. С другой рука, ведущий символ ('$' или '@') в массиве или хэше указывает, вы получаете единственное значение (скаляр) или множественное число (список).

35 голосов
/ 18 сентября 2008
@hash{@keys} = undef;

Синтаксис здесь, где вы ссылаетесь на хеш с @, представляет собой фрагмент хеша. Мы в основном говорим, что $hash{$keys[0]} И $hash{$keys[1]} И $hash{$keys[2]} ... - это список в левой части от =, lvalue, и мы присваиваем этот список, который фактически входит в хеш и устанавливает значения для всех именованных ключей. В этом случае я указал только одно значение, так что это значение входит в $hash{$keys[0]}, а все остальные хеш-записи автоматически оживляются (оживают) с неопределенными значениями. [Моим первоначальным предложением здесь было установить выражение = 1, которое установило бы один ключ на 1, а остальные на undef. Я изменил его для согласованности, но, как мы увидим ниже, точные значения не имеют значения.]

Когда вы поймете, что lvalue, выражение в левой части от =, является списком, построенным из хеша, тогда у вас появится какой-то смысл, почему мы используем этот @. [За исключением того, что я думаю, что это изменится в Perl 6.]

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

if ($hash{$key} == 1) # then key is in the hash

вместо

if (exists $hash{$key}) # then key is in the set

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

15 голосов
/ 19 сентября 2008

Обратите внимание: если вы набираете if ( exists $hash{ key } ), это не слишком много для вас (что я предпочитаю использовать, поскольку интерес представляет скорее наличие ключа, а не истинность его значения), тогда вы можете использовать короткая и сладкая

@hash{@key} = ();
7 голосов
/ 21 сентября 2008

Я всегда думал, что

foreach my $item (@array) { $hash{$item} = 1 }

был, по крайней мере, хорош и удобочитаем.

7 голосов
/ 19 сентября 2008

Здесь есть предположение, что наиболее эффективный способ сделать много: "Содержит ли массив X?" проверяет, чтобы преобразовать массив в хэш. Эффективность зависит от ограниченного ресурса, часто времени, а иногда пространства, а иногда и усилий программиста. Вы, по крайней мере, удваиваете объем используемой памяти, сохраняя список и хэш списка одновременно. Кроме того, вы пишете больше оригинального кода, который вам понадобится для тестирования, документирования и т. Д.

В качестве альтернативы обратите внимание на модуль List :: MoreUtils, в частности функции any(), none(), true() и false(). Все они принимают блок в качестве условия и список в качестве аргумента, аналогично map() и grep():

print "At least one value undefined" if any { !defined($_) } @list;

Я провел быстрый тест, загрузив половину / usr / share / dict / words в массив (25000 слов), а затем искал одиннадцать слов, выбранных по всему словарю (каждому 5000-му слову) в массиве, используя и метод массива в хэш, и функция any() из List :: MoreUtils.

На Perl 5.8.8, построенном из исходного кода, метод «массив в хеш» работает почти в 1100 раз быстрее, чем метод any() (в 1300 раз быстрее, чем в упакованном Perl 5.8.7 Ubuntu 6.06).

Однако это еще не все - преобразование массива в хэш занимает около 0,04 секунды, что в этом случае снижает эффективность использования метода массива в хэш-функции в 1,5–2 раза быстрее, чем метод any(). Все еще хорошо, но не так звездно.

Мне кажется, что метод массива-хеша превзойдет any() в большинстве случаев, но я чувствовал бы себя намного лучше, если бы у меня были более надежные метрики (много тестов, приличная статистика анализ, может быть, какой-то большой алгоритмический анализ каждого метода и т. д.) В зависимости от ваших потребностей, List :: MoreUtils может быть лучшим решением; это, конечно, более гибкий и требует меньше кодирования. Помните, преждевременная оптимизация - это грех ...:)

5 голосов
/ 19 сентября 2008

В Perl 5.10 есть оператор ~~, близкий к волшебному:

sub invite_in {
    my $vampires = [ qw(Angel Darla Spike Drusilla) ];
    return ($_[0] ~~ $vampires) ? 0 : 1 ;
}

Смотрите здесь: http://dev.perl.org/perl5/news/2007/perl-5.10.0.html

4 голосов
/ 01 октября 2011

Также стоит отметить полноту, мой обычный метод сделать это с двумя массивами одинаковой длины @keys и @vals, которые вы бы предпочли, были хэш ...

my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);

3 голосов
/ 18 сентября 2008

Решение Ральди может быть ужесточено до этого ('=>' от оригинала не требуется):

my %hash = map { $_,1 } @array;

Эту технику также можно использовать для преобразования текстовых списков в хэши:

my %hash = map { $_,1 } split(",",$line)

Кроме того, если у вас есть строка значений, подобная этой: "foo = 1, bar = 2, baz = 3", вы можете сделать это:

my %hash = map { split("=",$_) } split(",",$line);

[ПРАВИТЬ, чтобы включить]


Другое предлагаемое решение (которое занимает две строки):

my %hash;
#The values in %hash can only be accessed by doing exists($hash{$key})
#The assignment only works with '= undef;' and will not work properly with '= 1;'
#if you do '= 1;' only the hash key of $array[0] will be set to 1;
@hash{@array} = undef;
2 голосов
/ 19 сентября 2008

Вы также можете использовать Perl6 :: Junction .

use Perl6::Junction qw'any';

my @arr = ( 1, 2, 3 );

if( any(@arr) == 1 ){ ... }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...