ハッシュエントリをランダムに選択する必要があるため、そうします
h = {1 => 'one', 2 => 'two', 3 => 'three'}
k = h.keys.sample
result = h[k]
新しい配列を作成するのでh.keys
、私はそれが好きではありません。毎回新しい配列を作成しないようにする方法はありますか?
ハッシュエントリをランダムに選択する必要があるため、そうします
h = {1 => 'one', 2 => 'two', 3 => 'three'}
k = h.keys.sample
result = h[k]
新しい配列を作成するのでh.keys
、私はそれが好きではありません。毎回新しい配列を作成しないようにする方法はありますか?
最初に、ほとんどの人が言っていることを繰り返したいと思います。これはおそらく問題ではありません。
次に、 random keyではなくrandom valueが必要であるように思われることを指摘します。おそらく、コードのスニペットの例が実際に何をしているのかを示していないからです。
ランダム値が頻繁に必要で、ハッシュの更新頻度が非常に低い場合は、ハッシュが変更されるたびに値をキャッシュし、キャッシュからランダム値を取得することをお勧めします。これを行う 1 つの方法は、次のようになります。
class RandomValueHash < Hash
def []=(k, v)
super(k, v)
@values = self.values
end
def sample_value
@values ||= self.values
@values.sample
end
end
rvh = RandomValueHash[{1 => 'one', 2 => 'two', 3 => 'three'}]
rvh.sample_value
# => "one"
rvh[4] = 'four'
rvh[5] = 'five'
rvh.sample_value
# => "four"
もちろん、値ではなくランダムなキーが本当に必要な場合は、まったく同じ概念が適用されます。いずれにせよ、これにより、値を取得するたびに配列を再作成する必要がなくなります。必要な場合にのみ作成します。
これは別の配列を生成しません。平均して、 hash_random_valueは指定されたハッシュの途中まで反復してランダムな値を生成します。
def hash_random_value(h)
i = rand(h.length)
h.each_with_index do |(_, v), i2|
return v if i == i2
end
end
h = {1 => 'one', 2 => 'two', 3 => 'three'}
hash_random_value(h)
そうは言っても、それを行う必要があることが確実な場合にのみ最適化する必要があります。知ることができる唯一の方法は、コードをプロファイリングすることです。そうしないと、時期尚早の最適化を行っている可能性が高くなります。つまり、コードが複雑になり、バグが発生する可能性が高くなり、プログラムのパフォーマンスが低下することさえあります。あなたの元の解決策は私のものよりもはるかに理解しやすく、それが正しいことはすぐにわかります。
どうですか...
h = {1 => 'one', 2 => 'two', 3 => 'three'}
k = h.keys
...
result = h[k.sample]
result = h[k.sample]
好きなだけ何度でも実行できますが、k
配列が再生成されることはありません。ただし、k
時間のh
変更は再生成する必要があります。
補遺: 提案されたソリューションのいくつかのベンチマーク コードを投入しています。楽しみ。
#!/usr/bin/env ruby
require 'benchmark'
NUM_ITERATIONS = 1_000_000
def hash_random_value(h)
i = rand(h.length)
h.each_with_index do |(_, v), i2|
return v if i == i2
end
end
class RandomValueHash < Hash
def []=(k, v)
super(k, v)
@values = self.values
end
def sample_value
@values ||= self.values
@values.sample
end
end
Benchmark.bmbm do |b|
h = {1 => 'one', 2 => 'two', 3 => 'three'}
b.report("original proposal") do
NUM_ITERATIONS.times {k = h.keys.sample; result = h[k]}
end
b.report("hash_random_value") do
NUM_ITERATIONS.times {result = hash_random_value(h)}
end
b.report("manual keyset") do
k = h.keys
NUM_ITERATIONS.times {result = h[k.sample]}
end
rvh = RandomValueHash[{1 => 'one', 2 => 'two', 3 => 'three'}]
b.report("RandomValueHash") do
NUM_ITERATIONS.times {result = rvh.sample_value}
end
end
ランダム サンプルを大量に作成する必要があり、それを効率的にする必要がある場合、おそらく RubyHash
は問題のデータ構造またはストレージとして適切ではありません。Hash
たとえば、ハッシュへの書き込みごとに 20 個のランダムなサンプルを読み取る必要がある場合は、一緒に属性を保持するラッパー クラスでもArray
うまく機能する可能性があります。
それが機能するかどうかは、読み取りと書き込みの比率に依存するだけでなく、問題データの論理構造にも関係します (ソリューションでの表現方法とは対照的に)。
しかし、問題の再考に着手する前に、影響を受けるコードでより高いパフォーマンスが実際に必要とされている必要があります。キーをフェッチするための顕著なコストがかかるようにするには、ハッシュをかなり大きくする必要があります。h.keys
ラップトップでハッシュに 100 万のエントリがある場合、約 250 ミリ秒かかります。
あまり。ハッシュにはインデックスがないため、それらを配列に変換してランダムなインデックスを選択するか、ハッシュをランダムな回数列挙します。どのメソッドが最速かをベンチマークする必要がありますが、新しいオブジェクトの作成を回避できるとは思えません。
オブジェクトを気にしない場合は、そのキーをランダムな回数シフトできますが、戻り値の配列を作成します。
巨大なハッシュがない限り、これは無意味な懸念です。Ruby は効率化の原動力ではありません。これが心配な場合は、C(++) を使用する必要があります。
このようなもの:
h.each_with_index.reduce(nil) {|m, ((_, v), i)|
rand(i + 1) == 0 ? v : m
}