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


80

Скажімо, у мене є масив, і я знаю, що буду робити багато "Чи містить масив X?" перевірки. Ефективний спосіб зробити це - перетворити цей масив на хеш, де ключі є елементами масиву, і тоді ви можете просто сказати

if ($ хеш {X}) {...}

Чи є простий спосіб зробити це перетворення масиву в хеш? В ідеалі він повинен бути досить універсальним, щоб взяти анонімний масив і повернути анонімний хеш.

Відповіді:


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

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

Для цього потрібно взяти кожен елемент масиву та об’єднати його в пару з «1». Коли цей список пар (ключ, 1, ключ, 1, ключ 1) призначається хешу, непарні з них стають ключами хешу, а парні - відповідними значеннями.


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

Це хеш-фрагмент, список значень з хешу, тому він отримує list-y @ попереду.

З документів :

Якщо вас бентежить, чому ви використовуєте "@" там на зрізі хешу замість "%", подумайте про це так. Тип дужки (квадратний або фігурний) визначає, чи буде це масив чи хеш, який розглядається. З іншого боку, провідний символ ('$' або '@') на масиві чи хеші вказує, чи повертаєте ви сингулярне значення (скаляр) чи множину (список).


1
Ого, я ніколи не чув (і не думав) про це. Дякую! У мене проблеми з розумінням того, як це працює. Чи можете ви додати пояснення? Зокрема, як ви можете взяти хеш з назвою% hash і звернутися до нього зі знаком @?
ралді

2
raldi: це фрагмент хешу, список значень з хешу, тому він отримує list-y @ попереду. Див. Perldoc.perl.org/perldata.html#Slices - особливо останній абзац розділу
ysth

Ви повинні додати це до своєї відповіді!
ралді

Не могли б ви пояснити RHS? Дякую.
Susheel Javadi

1
(список) x $ число копіює список $ число разів. Використання масиву в скалярному контексті повертає кількість елементів, тому (1) x @array - це список 1s тієї ж довжини, що і @array.
Моріц

39
@hash{@keys} = undef;

Синтаксис тут, де ви посилаєтесь на хеш за допомогою, @є фрагментом хешу. В основному ми говоримо $hash{$keys[0]}І $hash{$keys[1]}І $hash{$keys[2]}... це список у лівій частині значення =, значення l, і ми присвоюємо цьому списку, який фактично переходить у хеш і встановлює значення для всіх названих ключів. У цьому випадку я вказав лише одне значення, так що це значення переходить $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, а деякі інші рішення обертають колеса, щоб створити масив значень, на які слід входити хеш; повністю витрачені зусилля).


1
Цей є кращим над іншим, оскільки він не створює тимчасовий список для ініціалізації хешу. Це повинно бути швидшим і споживати менше пам'яті.
Леон Тіммерманс

1
Морозний: Спочатку потрібно оголосити "мій% хеш", потім зробити "@hash {@arr} = 1" (без "мого").
Michael Carman

8
= (), а не = undefпросто для послідовності при неявному використанні undef для всіх значень, а не лише всіх після першого. (Як продемонстровано в цих коментарях, занадто просто побачити undefі думати, що його можна просто змінити на 1 і вплинути на всі хеш-значення.)
ysth

2
Оскільки тут значення закінчуються як "undef" (і, мабуть, з не зовсім тієї причини, з якої ви думаєте - як зазначив ysth), ви не можете просто використовувати хеш у коді, наприклад "if ($ hash {$ value})". Вам знадобиться "if (існує $ hash {$ value})".
Дейв Крос

2
Було б непогано, якби ви відредагували свою відповідь і вказали, що її потрібно використовувати з існує, що існує ефективніше, ніж перевірка істинності шляхом фактичного завантаження хеш-значення, і що undef займає менше місця, ніж 1.
bhollis

16

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

@hash{@key} = ();

8

Я завжди так думав

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

було принаймні приємним та читабельним / ремонтопридатним.


7

Тут існує припущення, що найефективніший спосіб зробити багато "Чи містить масив X?" перевірки полягає у перетворенні масиву в хеш. Ефективність залежить від обмежених ресурсів, часто часу, але іноді місця, а іноді зусиль програміста. Ви принаймні подвоюєте споживану пам'ять, одночасно зберігаючи список і хеш списку. До того ж ви пишете більш оригінальний код, який вам потрібно буде протестувати, задокументувати тощо.

В якості альтернативи, зверніть увагу на модуль Список :: MoreUtils, в зокрема , функції any(), none(), true()і false(). Усі вони беруть блок як умовний, а список як аргумент, подібний до map()та grep():

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

Я провів швидкий тест, завантаживши половину / usr / share / dict / слів до масиву (25000 слів), потім шукав одинадцять слів, вибраних із цілого словника (кожне 5000-те слово) у масиві, використовуючи обидва масиву -to-хеш-метод та any()функція з List :: MoreUtils.

У Perl 5.8.8, побудованому з вихідного коду, метод масиву до хешу працює майже в 1100 разів швидше, ніж any()метод (на 1300 разів швидший за пакету Perl 5.8.7 Ubuntu 6.06).

Однак це не вся історія - перетворення масиву в хеш займає близько 0,04 секунди, що в цьому випадку знижує ефективність часу методу масиву в хеш в 1,5 рази швидше, ніж any()метод. Все ще добре, але не настільки зоряне.

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


Це не відповідає на запитання. Він також пропускає суть ... перетворення масиву в хеш відбувається лише один раз ... загальний обсяг 0,04 секунди (у 2008 р.) Додається до часу роботи програми, тоді як пошук відбувається багато разів.
Jim Balter

2
Я намагався вирішити основну проблему, а не просто відповісти на питання. List::MoreUtilsможе бути або не бути відповідним методом, залежно від випадку використання. Ваш варіант використання може мати багато запитів; інші можуть цього не робити. Справа в тому, що як перетворення масиву в хеш, так і List::MoreUtilsвирішення основної проблеми визначення членства; знання кількох підходів дозволяє вибрати найкращий метод для конкретного випадку використання.
arclight

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
Якщо це робити кілька разів для великого масиву, це потенційно буде набагато повільніше.
ysth

1
Це "оператор розумних матчів" :)
brian d foy

5

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

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


4
Звичайна ідіома для @keys-1є $#keys.
Стефан Маєвський

@StefanMajewsky Я давно не бачив, щоб такий використовувався. Я сам тримаюся подалі від цього - це негарно.
Тамзін Блейк

3

Рішення Ральді можна посилити до цього ("=>" з оригіналу не потрібно):

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;

1
Різниця між $ _ => 1 і $ _, 1 є суто стилістичною. Особисто я віддаю перевагу =>, оскільки, схоже, посилання ключ / значення вказується більш чітко. Ваше рішення @hash {@array} = 1 не працює. Лише одне зі значень (те, що пов'язане з першим ключем у @array) отримує значення 1.
Dave Cross

2

Ви також можете використовувати Perl6 :: Junction .

use Perl6::Junction qw'any';

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

if( any(@arr) == 1 ){ ... }

1
Якщо це робити кілька разів для великого масиву, це потенційно буде набагато повільніше.
ysth

1
Насправді робити це один раз набагато повільніше. він повинен створити об’єкт. Тоді незабаром після цього він знищить цей об’єкт. Це лише приклад того, що можливо.
Бред Гілберт,

1

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


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;

%hash = map{ $_, undef } @keylist
Бред Гілберт,

1
#!/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'
        };

Тут hasref здається більш ніж трохи роздутим.
bobbogo

0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.