web-dev-qa-db-ja.com

Rubyで配列をインデックスハッシュに変換します

配列があり、ハッシュを作成して「Xは配列に含まれていますか?」.

Perlには、これを行う簡単な(そして高速の)方法があります。

my @array = qw( 1 2 3 );
my %hash;
@hash{@array} = undef;

これにより、次のようなハッシュが生成されます。

{
    1 => undef,
    2 => undef,
    3 => undef,
}

Rubyで思いついた最高のものは:

array = [1, 2, 3]
hash = Hash[array.map {|x| [x, nil]}]

与えるもの:

{1=>nil, 2=>nil, 3=>nil}

より良いRuby方法はありますか?

編集1

いいえ、Array.include?良い考えではありません。その遅い。 O(1)の代わりにO(n)でクエリを実行します。私の例の配列には簡潔にするために3つの要素がありました。

#!/usr/bin/Ruby -w
require 'benchmark'

array = (1..1_000_000).to_a
hash = Hash[array.map {|x| [x, nil]}]

Benchmark.bm(15) do |x|
    x.report("Array.include?") { 1000.times { array.include?(500_000) } }
    x.report("Hash.include?") { 1000.times { hash.include?(500_000) } }
end

生産物:

                     user     system      total        real
Array.include?  46.190000   0.160000  46.350000 ( 46.593477)
Hash.include?    0.000000   0.000000   0.000000 (  0.000523)
42
derobert

ハッシュが必要なのがメンバーシップだけの場合は、 Set の使用を検討してください。

セットする

Set は、重複のない順序付けされていない値のコレクションを実装します。これは、Arrayの直感的な相互運用機能とHashの高速ルックアップのハイブリッドです。

Set は、 Enumerable オブジェクト(eachを実装)で簡単に使用できます。初期化メソッドとバイナリ演算子のほとんどは、セットと配列のほかにジェネリック Enumerable オブジェクトを受け入れます。 Enumerable オブジェクトは、 to_set メソッドを使用して Set に変換できます。

Set はハッシュをストレージとして使用するため、次の点に注意する必要があります。

  • 要素の等価性は、Object#eql?およびObject#hashに従って決定されます。
  • Setは、各要素のIDが保存中に変更されないと想定します。セットの要素を変更すると、セットが信頼できない状態になります。
  • 文字列を保存する場合、元の文字列が既に凍結されていない限り、文字列の凍結コピーが代わりに保存されます。

比較

比較演算子<><=および>=は、{proper _、} {subset?、superset?}メソッドの省略形として実装されています。ただし、セットのすべてのペアが比較可能であるわけではないため、<=>演算子は意図的に省略されています。 (たとえば、{x、y}対{x、z})

require 'set'
s1 = Set.new [1, 2]                   # -> #<Set: {1, 2}>
s2 = [1, 2].to_set                    # -> #<Set: {1, 2}>
s1 == s2                              # -> true
s1.add("foo")                         # -> #<Set: {1, 2, "foo"}>
s1.merge([2, 6])                      # -> #<Set: {1, 2, "foo", 6}>
s1.subset? s2                         # -> false
s2.subset? s1                         # -> true

[...]

パブリッククラスメソッド

new(enum = nil)

指定された列挙可能なオブジェクトの要素を含む新しいセットを作成します。

ブロックが指定されている場合、enumの要素は指定されたブロックによって前処理されます。

43
rampion

これを試してください:

a=[1,2,3]
Hash[a.Zip]
22
edx

この非常に便利なトリックを行うことができます:

Hash[*[1, 2, 3, 4].map {|k| [k, nil]}.flatten]
=> {1=>nil, 2=>nil, 3=>nil, 4=>nil}
14
viebel

「配列にXが含まれていますか?」 Array#include? を使用する必要があります。

編集(OPの追加に応じて):

ルックアップ時間を短縮したい場合は、セットを使用します。すべてのnilsを指すハッシュを持つのはばかげています。 Array#to_setを使用した変換も簡単なプロセスです。

require 'benchmark'
require 'set'

array = (1..1_000_000).to_a
set = array.to_set

Benchmark.bm(15) do |x|
    x.report("Array.include?") { 1000.times { array.include?(500_000) } }
    x.report("Set.include?") { 1000.times { set.include?(500_000) } }
end

私のマシンでの結果:

                     user     system      total        real
Array.include?  36.200000   0.140000  36.340000 ( 36.740605)
Set.include?     0.000000   0.000000   0.000000 (  0.000515)

変換が不要になるように、配列の代わりに、最初からセットを使用することを検討する必要があります。

9
Zach Langley

このハッシュを構築するためのワンショット巧妙な方法がないことはかなり確信しています。私の傾向は、単に明示的であり、私がやっていることを述べることです:

hash = {}
array.each{|x| hash[x] = nil}

それは特にエレガントに見えませんが、それは明確で、仕事をします。

FWIW、元の提案(Ruby 1.8.6の下)は機能していないようです。「ArgumentError:奇数引数のハッシュ」エラーが表示されます。リテラル、偶数長の値のリスト:

Hash[a, 1, b, 2] # => {a => 1, b => 2}

だから私はあなたのコードを次のように変更しようとしました:

hash = Hash[*array.map {|x| [x, nil]}.flatten]

しかし、パフォーマンスはひどいです:

#!/usr/bin/Ruby -w
require 'benchmark'

array = (1..100_000).to_a

Benchmark.bm(15) do |x|
  x.report("assignment loop") {hash = {}; array.each{|e| hash[e] = nil}}
  x.report("hash constructor") {hash = Hash[*array.map {|e| [e, nil]}.flatten]}
end

与える

                     user     system      total        real
assignment loop  0.440000   0.200000   0.640000 (  0.657287)
hash constructor  4.440000   0.250000   4.690000 (  4.758663)

ここで何かを見逃していない限り、単純な割り当てループがこのハッシュを構築する最も明確で効率的な方法のようです。

6
chrismear

ランプオンは私を打ち負かしました。セットが答えかもしれません。

できるよ:

require 'set'
set = array.to_set
set.include?(x)
5
mtyaka

ハッシュを作成する方法は適切に見えます。私はirbの周りに泥がありました、これは別の方法です

>> [1,2,3,4].inject(Hash.new) { |h,i| {i => nil}.merge(h) }
=> {1=>nil, 2=>nil, 3=>nil, 4=>nil}
4
dylanfm

chrismear が作成よりも代入を使用することのポイントは素晴らしいと思います。全体をもう少しRuby風にするには、各要素にnil以外の何かotherを割り当てることをお勧めします。

hash = {}
array.each { |x| hash[x] = 1 } # or true or something else "truthy"
...
if hash[376]                   # instead of if hash.has_key?(376)
  ...
end

nilへの割り当ての問題は、has_key? の代わりに []、以来[]は、nilに指定されたキーがない場合、Hash(マーカー値)を提供します。あなたはcould別のデフォルト値を使用してこれを回避できますが、なぜ余分な作業を行うのですか?

# much less elegant than above:
hash = Hash.new(42)
array.each { |x| hash[x] = nil }
...
unless hash[376]
  ...
end
2
James A. Rosen

ここでの目標を誤解しているのかもしれません。 Xが配列に含まれているかどうかを知りたい場合は、array.include?( "X")を実行してみませんか?

1
capotej

ハッシュ値がわからない場合

irb(main):031:0> a=(1..1_000_000).to_a ; a.length
=> 1000000
irb(main):032:0> h=Hash[a.Zip a] ; h.keys.length
=> 1000000

デスクトップで数秒かかります。

1
telent

これまでの提案でベンチマークを行うと、chrismearとGaiusの割り当てベースのハッシュ作成が私のマップメソッドよりも若干速くなります(そして、nilの割り当てはtrueの割り当てよりも少し速くなります)。 mtyakaとrampionのSetの提案は、作成が約35%遅くなります。

ルックアップに関しては、hash.include?(x)は_hash[x]_よりも非常に高速です。どちらもset.include?(x)の2倍の速度です。

_                user     system      total        real
chrismear   6.050000   0.850000   6.900000 (  6.959355)
derobert    6.010000   1.060000   7.070000 (  7.113237)
Gaius       6.210000   0.810000   7.020000 (  7.049815)
mtyaka      8.750000   1.190000   9.940000 (  9.967548)
rampion     8.700000   1.210000   9.910000 (  9.962281)

                user     system      total        real
times      10.880000   0.000000  10.880000 ( 10.921315)
set        93.030000  17.490000 110.520000 (110.817044)
hash-i     45.820000   8.040000  53.860000 ( 53.981141)
hash-e     47.070000   8.280000  55.350000 ( 55.487760)
_

ベンチマークコードは次のとおりです。

_#!/usr/bin/Ruby -w
require 'benchmark'
require 'set'

array = (1..5_000_000).to_a

Benchmark.bmbm(10) do |bm|
    bm.report('chrismear') { hash = {}; array.each{|x| hash[x] = nil} }
    bm.report('derobert')  { hash = Hash[array.map {|x| [x, nil]}] }
    bm.report('Gaius')     { hash = {}; array.each{|x| hash[x] = true} }
    bm.report('mtyaka')    { set = array.to_set }
    bm.report('rampion')   { set = Set.new(array) }
end

hash = Hash[array.map {|x| [x, true]}]
set = array.to_set
array = nil
GC.start

GC.disable
Benchmark.bmbm(10) do |bm|
    bm.report('times')  { 100_000_000.times { } }
    bm.report('set')    { 100_000_000.times { set.include?(500_000) } }
    bm.report('hash-i') { 100_000_000.times { hash.include?(500_000) } }
    bm.report('hash-e') { 100_000_000.times { hash[500_000] } }
end
GC.enable
_
1
derobert

ハッシュを使用してルックアップをキャッシュする適切な方法を次に示します。

a = (1..1000000).to_a
h = Hash.new{|hash,key| hash[key] = true if a.include? key}

それが行うことのほとんどは、新しいハッシュ値のデフォルトコンストラクタを作成し、それが配列内にある場合はキャッシュに「true」を格納します(そうでない場合はnil)。これにより、すべての要素を使用しない場合に備えて、キャッシュへの遅延読み込みが可能になります。

0
zenazn

このPerlコードと同等のものを探している場合:

grep {$_ eq $element} @array

単純なRubyコードを使用できます:

array.include?(element)
0
Sophie Alpert

ハッシュが[0,0,0,1,0]の場合、これは0を保持します

  hash = {}
  arr.each_with_index{|el, idx| hash.merge!({(idx + 1 )=> el }) }

戻り値 :

  # {1=>0, 2=>0, 3=>0, 4=>1, 5=>0}
0
Trip