В Perl, как создать хэш, ключи которого поступают из данного массива?
предположим, у меня есть массив, и я знаю, что буду делать много "содержит ли массив X?" проверяет. Эффективный способ сделать это-превратить этот массив в хэш, где ключи являются элементами массива, а затем вы можете просто сказать
if($hash{X}) { ... }
есть ли простой способ сделать это преобразование массива в хэш? В идеале он должен быть достаточно универсальным, чтобы взять анонимный массив и вернуть анонимный хэш.
14 ответов
%hash = map { $_ => 1 } @array;
это не так коротко, как " @hash{@array} = ..."решения, но эти требуют, чтобы хэш и массив уже были определены где-то еще, в то время как этот может взять анонимный массив и вернуть анонимный хэш.
что это делает, это взять каждый элемент в массиве и соединить его с "1". Когда этот список пар (key, 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 слов), затем искал одиннадцать слов, выбранных из всего словаря (каждое 5000th слово) в массиве, используя как метод array-to-hash, так и метод any()
функция из списка:: MoreUtils.
на Perl 5.8.8, построенном из источника, метод array-to-hash работает почти 1100x быстрее, чем any()
метод (1300X быстрее под Ubuntu 6.06 упакован Perl 5.8.7.)
это не полная история, однако-преобразование массива в хэш занимает около 0,04 секунды, что в этом случае убивает эффективность времени метода массива в хэш до 1,5 x-2x быстрее, чем any()
метод. Еще хорошо, но не так звездный.
мое внутреннее чувство заключается в том, что метод array-to-hash будет бить 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
вы также можете использовать Perl6:: Junction.
use Perl6::Junction qw'any';
my @arr = ( 1, 2, 3 );
if( any(@arr) == 1 ){ ... }
решение Raldi может быть затянуто до этого ('=>'из оригинала не требуется):
my %hash = map { $_,1 } @array;
этот метод также может быть использован для преобразования текстовых списков в хэши:
my %hash = map { $_,1 } split(",",$line)
кроме того, если у вас есть строка значений, как это: "foo=1, bar=2, baz=3" Вы можете сделать это:
my %hash = map { split("=",$_) } split(",",$line);
[EDIT to include]
другое предлагаемое решение (которое занимает две строки):
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 массивами одинаковой длины @keys
и @vals
который вы предпочли бы хэш...
my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);
Если вы делаете много теоретико-множественных операций-вы также можете использовать Set:: Scalar или аналогичного модуля. Тогда $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;
вы также можете проверить Tie:: 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'
};