В Perl, как мне создать хэш, ключи которого берутся из заданного массива?
Вопрос
Допустим, у меня есть массив, и я знаю, что мне придется выполнять множество проверок "Содержит ли массив X?".Эффективный способ сделать это - превратить этот массив в хэш, где ключами являются элементы массива, а затем вы можете просто сказать
if($hash{X}) { ... }
Есть ли простой способ выполнить это преобразование массива в хэш?В идеале он должен быть достаточно универсальным, чтобы принимать анонимный массив и возвращать анонимный хэш.
Решение
%hash = map { $_ => 1 } @array;
Это не такое короткое решение, как "@hash{@array} = ...", но для этих решений требуется, чтобы хэш и массив уже были определены где-то в другом месте, тогда как это может принимать анонимный массив и возвращать анонимный хэш.
Что это делает, так это берет каждый элемент в массиве и связывает его с "1".Когда этот список пар (ключ, 1, key, 1, key 1) присваивается хэшу, нечетные пары становятся ключами хэша, а четные - соответствующими значениями.
Другие советы
@hash{@array} = (1) x @array;
Это фрагмент хэша, список значений из хэша, поэтому перед ним отображается список-y @.
От документы:
Если вы не понимаете, почему вы используете символ "@" в срезе хэша вместо символа "%", подумайте об этом так. Тип скобки (квадратная или фигурная) определяет, является ли это просматриваемым массивом или хэшем.С другой силы, ведущие символ ('$' или '@') на массив или хэш-указывает, будет ли вы получаете обратно исключительное значение (скалярный) или множественный (список).
@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
, и некоторые другие решения вращают колесики для создания массива значений для перехода в хэш;совершенно напрасные усилия).
Обратите внимание, что при вводе if ( exists $hash{ key } )
если это не слишком большая работа для вас (которую я предпочитаю использовать, поскольку предметом интереса на самом деле является наличие ключа, а не правдивость его значения), то вы можете использовать короткий и приятный
@hash{@key} = ();
Здесь предполагается, что наиболее эффективный способ выполнить множество проверок "Содержит ли массив 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()
функция из списка::MoreUtils.
В Perl 5.8.8, построенном на основе исходного кода, метод преобразования массива в хэш выполняется почти в 1100 раз быстрее, чем any()
метод (в 1300 раз быстрее под управлением Ubuntu 6.06 с пакетом Perl 5.8.7.)
Однако это не полная история - преобразование массива в хэш занимает около 0,04 секунды, что в данном случае снижает временную эффективность метода array-to-hash в 1,5-2 раза быстрее, чем any()
способ.Все еще хорош, но далеко не так великолепен.
Мое внутреннее чутье подсказывает, что метод преобразования массива в хэш будет превзойден any()
в большинстве случаев, но я чувствовал бы себя намного лучше, если бы у меня были более надежные показатели (множество тестовых примеров, приличный статистический анализ, возможно, какой-нибудь масштабный алгоритмический анализ каждого метода и т.д.) В зависимости от ваших потребностей, лучшим решением может быть List::MoreUtils;это, безусловно, более гибко и требует меньше кодирования.Помните, что преждевременная оптимизация - это грех...:)
Я всегда думал, что
foreach my $item (@array) { $hash{$item} = 1 }
было, по крайней мере, приятно и читабельно / ремонтопригодно.
В 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
Также стоит отметить для полноты картины мой обычный метод для выполнения этого с помощью 2 массивов одинаковой длины @keys
и @vals
которые вы бы предпочли, чтобы были хэшем...
my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);
Решение Ральди может быть ужесточено до этого ('=>' из оригинала не требуется).:
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;
Вы также могли бы использовать Perl6:: Соединение.
use Perl6::Junction qw'any';
my @arr = ( 1, 2, 3 );
if( any(@arr) == 1 ){ ... }
Если вы выполняете много теоретико-множественных операций - вы также можете использовать Набор::Скалярный или аналогичный модуль.Тогда $s = Set::Scalar->new( @array )
создаст набор для вас - и вы сможете запросить его с помощью: $s->contains($m)
.
Вы можете поместить код в подпрограмму, если не хотите загрязнять свое пространство имен.
my $hash_ref =
sub{
my %hash;
@hash{ @{[ qw'one two three' ]} } = undef;
return \%hash;
}->();
Или даже лучше:
sub keylist(@){
my %hash;
@hash{@_} = undef;
return \%hash;
}
my $hash_ref = keylist qw'one two three';
# or
my @key_list = qw'one two three';
my $hash_ref = keylist @key_list;
Если вы действительно хотите передать ссылку на массив:
sub keylist(\@){
my %hash;
@hash{ @{$_[0]} } = undef if @_;
return \%hash;
}
my @key_list = qw'one two three';
my $hash_ref = keylist @key_list;
Возможно, вы также захотите проверить Ничья::IxHash, который реализует упорядоченные ассоциативные массивы.Это позволило бы вам выполнять оба типа поиска (хэш и индекс) для одной копии ваших данных.
#!/usr/bin/perl -w
use strict;
use Data::Dumper;
my @a = qw(5 8 2 5 4 8 9);
my @b = qw(7 6 5 4 3 2 1);
my $h = {};
@{$h}{@a} = @b;
print Dumper($h);
выдает (обратите внимание, что повторяющиеся ключи получают значение в наибольшей позиции в массиве - т.е. 8-> 2, а не 6)
$VAR1 = {
'8' => '2',
'4' => '3',
'9' => '1',
'2' => '5',
'5' => '4'
};