[Ruby 1.9-Feature#4147][Open] Array#sample で重みを指定したい

Feature #4147: Array#sample で重みを指定したい
http://redmine.ruby-lang.org/issues/show/4147

起票者: Yoji Ojima
ステータス: Open, 優先度: Normal

Array#sample にブロックを渡したとき、ブロックの戻り値を要素の重みとして使用するのはいかがでしょうか。

下記のサンプルで、“大吉” が “凶” の 1000 倍の確率で選択されるようにしたいです。

omikuji_box = [
{:name => “大吉”, :weight => 1000},
{:name => “中吉”, :weight => 100},
{:name => “小吉”, :weight => 10},
{:name => “凶”, :weight => 1}
]
omikuji = omikuji_box.sample {|v| v[:weight] }
puts omikuji[:name]

チケット #4147 が更新されました。 (by Shyouhei U.)

ステータス OpenからFeedbackに変更

実装のたたき台があるとなおよいかとおもいます。
まずRubyで同等の処理をするコードを書いてみるというのはどうでしょう。
妥当であれば、取り込まれたり、あるいは高速化のためにCで書きなおされたりするかもしれません。

チケット #4147 が更新されました。 (by Shota F.)

Rubyで実装してみました。あまりコストとか速度は気にしていないので速度は気にせず。

class Array
def sample(n=1)
if block_given?
self.size.times.map do |i|
Array.new(yield(self[i]), self[i])
end.flatten.sample(n)
else
n == 1 ? self[rand(self.size)] : Array.new(n){
self[rand(self.size)] }
end
end
end

– テスト

def a
[1,2,3].sample { |v| v*100 }
end

def b
[1,2,3].sample { |v| v*100 }
end
ary = 100.times.map{a}
bry = 100.times.map{b}

result_a = {}

ary.each do |i|
result_a[i] ||= 0
result_a[i] += 1
end

result_b = {}

bry.each do |i|
result_b[i] ||= 0
result_b[i] += 1
end

aは i*10の重みあり。 bは重みなし。 {i => 出現回数}

p result_a #=> {2=>31, 3=>56, 1=>13}
p result_b #=> {2=>37, 3=>45, 1=>18}

– sora_h

チケット #4147 が更新されました。 (by Shota F.)

ミスがあったので訂正。

– テスト

def a
[1,2,3].sample { |v| v*100 }
end

def b
[1,2,3].sample { |v| v }
end
ary = 100.times.map{a}
bry = 100.times.map{b}

result_a = {}

ary.each do |i|
result_a[i] ||= 0
result_a[i] += 1
end

result_b = {}

bry.each do |i|
result_b[i] ||= 0
result_b[i] += 1
end

aは i*10の重みあり。 bは重みなし。 {i => 出現回数}

p result_a #=> {3=>56, 1=>13, 2=>31}
p result_b #=> {2=>34, 3=>46, 1=>20}

– sora_h

チケット #4147 が更新されました。 (by Yoji Ojima)

メソッドの引数を考慮しない簡易的なコードでよろしければ。

class Array
alias :org_sample :sample

def sample
if block_given?
prob_map = collect {|v| yield(v) }
rn = rand * prob_map.inject(0) {|r, v| r += v }
prob_map.each_with_index do |v, i|
rn -= v
return self[i] if rn < 0
end
else
org_sample
end
end
end

チケット #4147 が更新されました。 (by Yoji Ojima)

一週間経って特に反対意見はないようですが、実際にパッチを書いたら取り込んでいただけるものなのでしょうか。積極的な賛成がなければダメでしょうか。

チケット #4147 が更新されました。 (by Yoji Ojima)

すみません、入れ違いでした。

チケット #4147 が更新されました。 (by Shyouhei U.)

ステータス FeedbackからAssignedに変更
担当者 Shyouhei U.にセット

じゃあ反対ないので実装はともかく、この仕様は基本入れる方向で考えましょう。反対の人は意思表示お早めに。

実装はCで書かれたものがあると取り込まれるまでは早くなる可能性が高いと思います。なければ俺が書くまでお待ちください。

チケット #4147 が更新されました。 (by Hiro A.)

あさりです。

失礼ですが、これ、標準に入れるほどの需要があるんでしょうか。

積極的に反対という訳ではないですが、「あると便利」くらいだったらgemとかの方が無難だと思います。

チケット #4147 が更新されました。 (by Yoji Ojima)

「均一確率での標本抽出は標準で必要だが、その重み指定は不要」とする合理的な理由があるかというのがポイントではないかと考えます。

需要という意味ではさほど違いがあるようには思えませんが、どんなものでしょうか。

On Dec 18, 2010, at 9:15 PM, Yoji Ojima wrote:

需要という意味ではさほど違いがあるようには思えませんが、どんなものでしょうか。

需要だけが標準に入れるかどうかの物差しである、という意味に取られたのでしたら
私の言葉足らずでした。すみません。

「既に在るものを外す」には「今無いものを入れる」よりも多くの労力が必要だと思うので、
今から入れるものには慎重になるべき、という意味でした。ご理解下さい。

1.9でMath.gammaとMath.lgammaが入って、それをJRubyで実装する際に
ちょっと苦労したのでその時のことが思い出されたのでした。

チケット #4147 が更新されました。 (by Hiro A.)

ちょっと諄いようですが、私は「積極的に反対」というわけではありません。

チケット #4147 が更新されました。 (by Yoji Ojima)

これだと毎回 each することになりますよね。

もちろんそうですが、これはゲームでの利用を意図したもので、毎回と言うほど何度も繰り返し呼び出すことは想定していません。

モンテカルロシミュレーション等、速度が重要な場合は Ruby 自体が選択されないのではないかと思います。