背景
rubyで重み付きルーレット選択をする時のワンライナー。
rubyでルーレット
rubyにprefix sumまたはscan(mapとreduceが合わさったような関数)が用意されていればいいが、ないので、sumの部分は外出しで、次のように書いた。
ruby -e 'r=rand;s=0;p [0.1,0.3,0.6].map.with_index{|a,i|[s+=a,i]}.select{|s,i|s>r}.first[1]'
出力結果:
2
出力結果は0-origin。
[0.1,0.3,0.6]
の部分が重み付き配列として扱う。合計が1になるように正規化するか、合計値をrandにかけて扱う。
検証コード
ruby -e 'h=Hash.new(0);100000.times{r=rand;s=0;h[( [0.1,0.3,0.6].map.with_index{|a,i|[s+=a,i]}.select{|s,i|s>r}.first[1] )]+=1}; p h'
出力結果:
{2=>60055, 1=>30121, 0=>9824}
関数版
面倒なので、検証コードと共に。(重みの正規化付き)
ruby -e 'def roulette(list) r=rand*list.reduce(:+);s=0;list.map.with_index{|a,i|[s+=a,i]}.select{|s,i|s>r}.first[1] end; h=Hash.new(0); 100000.times{h[roulette([10,30,60])]+=1}; p h'
出力結果:
{2=>59775, 0=>9865, 1=>30360}
まとめ
prefix scanがあればもっと綺麗に書けそう。