В 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'
        };