it-swarm.com.ru

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

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

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

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

74
raldi
%hash = map { $_ => 1 } @array;

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

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

109
raldi
 @hash{@array} = (1) x @array;

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

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

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

41
moritz
@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, а некоторые другие решения поворачивают колесико, чтобы создать массив значений для перехода в хэш, полностью потраченные впустую усилия).

34
skiphoppy

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

@hash{@key} = ();
15
Aristotle Pagaltzis

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

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

был по крайней мере приятным и читаемым/ремонтопригодным.

7
Keith

Здесь есть предположение, что наиболее эффективный способ сделать много "Содержит ли массив 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-е слово) в массиве, используя оба массива -to-hash метод и функция 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 может быть лучшим решением; это, конечно, более гибкий и требует меньше кодирования. Помните, преждевременная оптимизация - это грех ... :)

7
arclight

В 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

5
RET

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

use Perl6::Junction qw'any';

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

if( any(@arr) == 1 ){ ... }
2
Brad Gilbert

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

my %hash = map { $keys[$_] => $vals[$_] } ([email protected]);

2
Tamzin Blake

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

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
Frosty

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

1
zby

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

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;
1
Brad Gilbert
#!/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
Mark Dibley

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

0
Dave G