В Perl, как мне создать хеш, ключи которого поступают из данного массива?

70

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

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

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

Теги:
arrays
hash

14 ответов

100
Лучший ответ
%hash = map { $_ => 1 } @array;

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

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

37
 @hash{@array} = (1) x @array;

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

От документы:

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

  • 1
    Ничего себе, я никогда не слышал (или думал) об этом. Спасибо! У меня проблемы с пониманием, как это работает. Можете ли вы добавить объяснение? В частности, как вы можете взять хеш с именем% hash и сослаться на него со знаком @?
  • 2
    raldi: это срез хеша, список значений из хеша, поэтому он получает список-y @ впереди. См. Perldoc.perl.org/perldata.html#Slices - в частности, последний абзац раздела
Показать ещё 4 комментария
33
@hash{@keys} = undef;

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

  • 1
    Этот вариант предпочтительнее другого, поскольку он не создает временный список для инициализации хэша. Это должно быть быстрее и потреблять меньше памяти.
  • 0
    Это не работает при тестировании: test.pl: my @arr = ("foo", "bar", "baz"); my @hash {@arr} = 1; синтаксическая ошибка в строке test.pl 2, рядом с "@hash {"
Показать ещё 7 комментариев
14

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

@hash{@key} = ();
7

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

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

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

Я провел быстрый тест, загрузив половину /usr/share/dict/words в массив (25000 слов), а затем поискал одиннадцать слов, выбранных из всего словаря (каждое 5000-е слово) в массиве, используя как метод 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,5x-2x быстрее, чем метод any(). Все еще хорошо, но не так близко, как звездный.

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

  • 0
    List :: MoreUtils - отличный совет, спасибо.
  • 0
    Это не отвечает на вопрос. Это также не учитывает точку ... преобразование массива в хеш происходит только один раз ... всего 0,04 секунды (в 2008 году), добавленных к времени выполнения программы, тогда как поиск происходит много раз.
Показать ещё 1 комментарий
6

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

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

был, по крайней мере, приятным и удобочитаемым/поддерживаемым.

5

В 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

  • 1
    Если сделать это несколько раз для большого массива, это может быть намного медленнее.
  • 1
    Это был бы «умный оператор матча» :)
2

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

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

  • 4
    Обычная идиома для @keys-1 - это $#keys .
  • 0
    @StefanMajewsky Я давно не видел, чтобы тот действительно использовался. Я держусь от этого подальше - это безобразно.
2

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

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

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

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

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

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

[EDIT включить]


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

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;
  • 1
    Разница между $ _ => 1 и $ _, 1 является чисто стилистической. Лично я предпочитаю =>, так как это, кажется, указывает на связь ключ / значение более явно. Ваше решение @hash {@array} = 1 не работает. Только одно из значений (связанное с первым ключом в @array) устанавливается в 1.
  • 0
    Спасибо тебе за пояснение. Я отредактировал ответ.
2

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

use Perl6::Junction qw'any';

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

if( any(@arr) == 1 ){ ... }
  • 1
    Если сделать это несколько раз для большого массива, это может быть намного медленнее.
  • 1
    На самом деле делать это один раз намного медленнее. это должно создать объект. Затем, вскоре после этого, он уничтожит этот объект. Это всего лишь пример того, что возможно.
1

Вы можете поместить код в подпрограмму, если вы не хотите загрязнять пространство имен.

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;
  • 0
    %hash = map{ $_, undef } @keylist
1

Если вы выполняете множество теоретико-множественных операций, вы также можете использовать Set:: Scalar или аналогичный модуль. Затем $s = Set::Scalar->new( @array ) построит для вас Set - и вы можете запросить его с помощью: $s->contains($m).

0
#!/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'
        };
  • 0
    Хасреф кажется здесь более чем раздутым.
0

Вы также можете проверить Tie:: IxHash, который реализует упорядоченные ассоциативные массивы. Это позволит вам выполнять оба типа поиска (хэш и индекс) на одной копии ваших данных.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню