配列があり、ハッシュを作成して「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方法はありますか?
いいえ、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)
ハッシュが必要なのがメンバーシップだけの場合は、 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の要素は指定されたブロックによって前処理されます。
これを試してください:
a=[1,2,3]
Hash[a.Zip]
この非常に便利なトリックを行うことができます:
Hash[*[1, 2, 3, 4].map {|k| [k, nil]}.flatten]
=> {1=>nil, 2=>nil, 3=>nil, 4=>nil}
「配列にXが含まれていますか?」 Array#include?
を使用する必要があります。
編集(OPの追加に応じて):
ルックアップ時間を短縮したい場合は、セットを使用します。すべてのnil
sを指すハッシュを持つのはばかげています。 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)
変換が不要になるように、配列の代わりに、最初からセットを使用することを検討する必要があります。
このハッシュを構築するためのワンショット巧妙な方法がないことはかなり確信しています。私の傾向は、単に明示的であり、私がやっていることを述べることです:
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)
ここで何かを見逃していない限り、単純な割り当てループがこのハッシュを構築する最も明確で効率的な方法のようです。
ランプオンは私を打ち負かしました。セットが答えかもしれません。
できるよ:
require 'set'
set = array.to_set
set.include?(x)
ハッシュを作成する方法は適切に見えます。私はirbの周りに泥がありました、これは別の方法です
>> [1,2,3,4].inject(Hash.new) { |h,i| {i => nil}.merge(h) }
=> {1=>nil, 2=>nil, 3=>nil, 4=>nil}
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
ここでの目標を誤解しているのかもしれません。 Xが配列に含まれているかどうかを知りたい場合は、array.include?( "X")を実行してみませんか?
ハッシュ値がわからない場合
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
デスクトップで数秒かかります。
これまでの提案でベンチマークを行うと、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
_
ハッシュを使用してルックアップをキャッシュする適切な方法を次に示します。
a = (1..1000000).to_a
h = Hash.new{|hash,key| hash[key] = true if a.include? key}
それが行うことのほとんどは、新しいハッシュ値のデフォルトコンストラクタを作成し、それが配列内にある場合はキャッシュに「true」を格納します(そうでない場合はnil)。これにより、すべての要素を使用しない場合に備えて、キャッシュへの遅延読み込みが可能になります。
このPerlコードと同等のものを探している場合:
grep {$_ eq $element} @array
単純なRubyコードを使用できます:
array.include?(element)
ハッシュが[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}