Perlで、キーが特定の配列から取得されるハッシュを作成するにはどうすればよいですか?


80

配列があり、「配列にはXが含まれていますか?」をたくさん実行することがわかっているとします。チェックします。これを行う効率的な方法は、その配列をハッシュに変換することです。ここで、キーは配列の要素であり、次のように言うことができます。

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

この配列からハッシュへの変換を行う簡単な方法はありますか?理想的には、匿名配列を取得して匿名ハッシュを返すのに十分な汎用性が必要です。

回答:


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

「@hash {@array} = ...」ソリューションほど短くはありませんが、これらのソリューションでは、ハッシュと配列が別の場所ですでに定義されている必要がありますが、これは匿名配列を取得して匿名ハッシュを返すことができます。

これは、配列内の各要素を取得して、それを「1」とペアにします。この(key、1、key、1、key 1)ペアのリストがハッシュに割り当てられると、奇数番号のペアがハッシュのキーになり、偶数番号のペアがそれぞれの値になります。


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

これはハッシュスライスであり、ハッシュからの値のリストであるため、リスト-y @を前に取得します。

ドキュメントから:

ハッシュスライスで「%」の代わりに「@」を使用する理由について混乱している場合は、次のように考えてください。角かっこのタイプ(正方形または中括弧)は、それが配列であるかハッシュであるかを決定します。一方、配列またはハッシュの先頭の記号( '$'または '@')は、単数形(スカラー)または複数形(リスト)のどちらを返すかを示します。


1
うわー、私はそれを聞いたことがない(または考えたことがない)。ありがとう!私はそれがどのように機能するか理解するのに苦労しています。説明を追加できますか?特に、%hashという名前のハッシュを取得し、@記号で参照するにはどうすればよいでしょうか。
raldi 2008

2
raldi:これはハッシュスライスであり、ハッシュからの値のリストであるため、リスト-y @を前に取得します。参照してくださいperldoc.perl.org/perldata.html#Slicesセクションの、特に最後の段落を-
ysth

あなたはそれをあなたの答えに加えるべきです!
raldi 2008

RHSについても説明していただけますか?ありがとう。
Susheel Javadi 2010

1
(リスト)x $ numberは、リスト$ numberを複製します。スカラーコンテキストで配列を使用すると、要素の数が返されるため、(1)x @arrayは、@ arrayと同じ長さの1のリストです。
moritz 2010

39
@hash{@keys} = undef;

でハッシュを参照しているここでの構文@は、ハッシュスライスです。基本的に、$hash{$keys[0]}AND $hash{$keys[1]}AND $hash{$keys[2]}...は左辺値の=の左側にあるリストであり、そのリストに割り当てます。このリストは実際にハッシュに入り、すべての名前付きキーの値を設定します。この場合、1つの値のみを指定したため、値はに$hash{$keys[0]}なり、他のハッシュエントリはすべて、未定義の値で自動的に有効になります(生き返ります)。[ここでの私の最初の提案は、式= 1に設定されていました。これにより、1つのキーが1に設定され、他のキーが1に設定されます。undefます。一貫性を保つために変更しましたが、以下で説明するように、正確な値は重要ではありません。]

=の左側にある式である左辺値がハッシュから作成されたリストであることに気付くと、なぜそれを使用しているのかが理解できるようになります。 @。[これはPerl6で変更されると思うことを除いて。]

ここでの考え方は、ハッシュをセットとして使用しているということです。重要なのは、私が割り当てている値ではありません。それはただ鍵の存在です。したがって、あなたがやりたいことは次のようなものではありません。

if ($hash{$key} == 1) # then key is in the hash

代わりに:

if (exists $hash{$key}) # then key is in the set

実際にexistsは、ハッシュの値を気にするよりもチェックを実行する方が効率的ですが、ここで重要なのは、ハッシュのキーだけでセットを表すという概念だけです。また、undefここで値と​​して使用することにより、値を割り当てるよりも少ないストレージスペースを消費するという指摘もありました。(また、値は重要ではないため、混乱が少なくなります。私のソリューションでは、ハッシュの最初の要素にのみ値を割り当てて他の要素を残します。他のundefいくつかのソリューションでは、カートホイールを回して値の配列を作成します。ハッシュ;完全に無駄な努力)。


1
これは、ハッシュを初期化するための一時的なリストを作成しないため、もう一方よりも適しています。これはより速く、より少ないメモリを消費するはずです。
Leon Timmermans

1
フロスティ:最初に「my%hash」を宣言してから、「@ hash {@arr} = 1」(「my」なし)を実行する必要があります。
マイケルカーマン

8
= ()、ではなく= undef、最初の値の後だけでなく、すべての値に暗黙的にundefを使用する際の一貫性のためだけです。(これらのコメントに示されているように、を見てundef、1に変更して、すべてのハッシュ値に影響を与えることができると考えるのは簡単すぎます。)
ysth 2008

2
ここでは値が「undef」になるため(おそらく、おそらくあなたが考える理由ではないので、ysthが指摘しているように)、「if($ hash {$ value})」のようなコードでハッシュを使用することはできません。「if(exists $ hash {$ value})」が必要です。
デイブクロス

2
あなたはそれが存在して使用する必要があることを指摘してあなたの答えを編集した場合は、実際のハッシュ値をロードすることによってtruthinessをチェックするよりも効率的であるが存在すること、素敵なことだ、とundefが1より少ないスペースを取ること
bhollis

16

タイピングif ( exists $hash{ key } )があまり手間がかからない場合(関心のある問題は、その値の真実性ではなく、実際にはキーの存在であるため、私が使用することを好む)、短くて甘いものを使用できることに注意してください

@hash{@key} = ();

8

私はいつも思っていた

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

少なくとも素晴らしく、読みやすく、保守可能でした。


7

ここには、多くの「配列にXが含まれていますか?」を実行するための最も効率的な方法であるという前提があります。チェックは、配列をハッシュに変換することです。効率は、リソースの不足、多くの場合時間、場合によってはスペース、場合によってはプログラマーの努力に依存します。リストとリストのハッシュを同時に保持することで、消費されるメモリを少なくとも2倍にします。さらに、テストや文書化などに必要な、よりオリジナルのコードを書いています。

代替、リストを見て:: MoreUtilsモジュールは、具体的な機能としてany()none()true()false()。彼らはすべての条件と引数としてリストと同様にブロックを取るmap()grep()

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

クイックテストを実行し、/ usr / share / dict / wordsの半分を配列(25000ワード)にロードしてから、両方の配列を使用して、配列内の辞書全体(5000ワードごと)から選択された11ワードを探しました。 -to-hashメソッドと any()List :: MoreUtilsの関数。

ソースから構築されたPerl5.8.8では、配列からハッシュへのメソッドは、 any()メソッド(Ubuntu6.06のパッケージ化されたPerl5.8.7では1300倍高速です)。

ただし、これは完全な話ではありません。配列からハッシュへの変換には約0.04秒かかります。この場合、配列からハッシュへの方法の時間効率は1.5倍から2倍速くなります。 any()メソッドのます。それでも良いですが、それほど恒星ではありません。

私の直感ではany()、ほとんどの場合、配列からハッシュへの方法が勝つだろうと思いますが、もっとしっかりしたメトリック(たくさんのテストケース、まともな統計分析、おそらくいくつかの大きなもの)があれば、はるかに気分が良くなります。 O各メソッドのアルゴリズム分析など)ニーズによっては、List :: MoreUtilsの方が適している場合があります。それは確かにより柔軟で、より少ないコーディングを必要とします。時期尚早の最適化は罪であることを忘れないでください... :)


これは質問に答えません。また、ポイントを見逃しています...配列からハッシュへの変換は1回だけ発生します...プログラムの実行時間に合計0.04秒(2008年)が追加されますが、ルックアップは何度も発生します。
ジムバルター2018年

2
質問に答えるだけでなく、根本的な問題を解決しようとしました。List::MoreUtilsユースケースに応じて、適切な方法である場合とそうでない場合があります。ユースケースには多くのルックアップがある場合があります。他の人はそうではないかもしれません。重要なのは、配列からハッシュへの変換とList::MoreUtils、メンバーシップを決定するという根本的な問題の解決の両方です。複数のアプローチを知っていると、特定のユースケースに最適な方法を選択できます。
アークライト2018年

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 2008

1
それは「スマートマッチオペレーター」
でした

5

また、完全性のために注目に値するのは、2つの同じ長さの配列@keys@valsこれを行うための私の通常の方法であり、ハッシュでした...

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


4
の通常のイディオムは@keys-1です$#keys
Stefan Majewsky 2012年

@StefanMajewsky私はそれが実際にしばらく使用されているのを見たことがありません。私はそれから離れています-それは醜いです。
Tamzin Blake 2012

3

Raldiのソリューションはこれまで引き締めることができます(オリジナルの「=>」は必要ありません):

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

この手法は、テキストリストをハッシュに変換するためにも使用できます。

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

さらに、「foo = 1、bar = 2、baz = 3」のような値の行がある場合は、次のようにすることができます。

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

[編集して含める]


提供される別のソリューション(2行かかります)は次のとおりです。

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)の一つが1に設定されます
デイブクロス

2

Perl6 :: Junctionを使用することもできます。

use Perl6::Junction qw'any';

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

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

1
大きなアレイに対して複数回実行すると、速度が大幅に低下する可能性があります。
ysth 2008

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);

与える(繰り返されるキーは配列の最大位置で値を取得することに注意してください-つまり、6ではなく8-> 2)

$VAR1 = {
          '8' => '2',
          '4' => '3',
          '9' => '1',
          '2' => '5',
          '5' => '4'
        };

hasrefは、ここでは少なからず誇張されているようです。
bobbogo 2014年

0

順序付けられた連想配列を実装するTie :: IxHashも確認することをお勧めします。これにより、データの1つのコピーに対して両方のタイプのルックアップ(ハッシュとインデックス)を実行できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.