重み付抽選機能を作る
重み付抽選機能を作る
登録されたデータを重み付けランダム抽選したいという要件は
Webでは意外とあるのではと思うのですが
今回、製作中のWebサイトで投稿された内容を
五段階のユーザーの評価の平均点に対応した頻度で表示する機能を作りました。
基本的な設計としては
■idと重みというセットのデータを複数登録していく
■登録完了後に、idを重みの数だけハッシュにセットしていく。
■このセットの中かidを無作為に抽出します。
具体的にはこんな感じのソースになりました。
class WghtdRndmExtrct
def initialize(arry = nil)
@key_cntr = 0
@rltt_cntr = 0
@wghtd_rltt = Hash.newunless arry ==nil then
addArry(arry)
end
enddef addArry(arry)
for elmnt in arry
addElmnt(elmnt[:wght],elmnt[:id])
end
enddef addElmnt(wght,id)
wght.times{
@wghtd_rltt[ @rltt_cntr ] = id
@rltt_cntr = @rltt_cntr + 1
}
@key_cntr = @key_cntr + 1
enddef keyCnt
return @key_cntr
enddef rlttCnt
return @rltt_cntr
enddef getRlltValue(key1,key2=1,key3=1,key4=1)
unless @wghtd_rltt == nil then
id = (key1.to_i * key2.to_i * key3.to_i + key4.to_i) % @rltt_cntr
return @wghtd_rltt[id]
end
endend
抽選用のHashが異様に大きくならない状態(100万レコード以内)であれば
パフォーマンス的にも問題なく動作します。
実際に抽選値を取得するgetRlltValueは今回それほど厳密さを求められる要件ではないので
それほどまじめに実装していませんが、
掛け算の結果が要素数(rltt_cntr)に比べて大きくなるようにしないと
HASHの最初のほうのIDに抽選結果が偏るので、その点を気をつけることを推奨します。
実際にこのユーティリティーを利用している例も書いておくと
def wrdChsr(usrId)
##本日日付を8桁キャラで取得する
t = Time.now
tdy = t.strftime('%Y%m%d')##パフォーマンスを考慮して抽選用HASHは一日に一度だけ再作成する。
if @@rllt_vrsn == nil || @@rllt_vrsn != tdy then##公開中のワードを取得する
wrds = MWrd.find(:all, :conditions => "pblc_dvsn = '01'")##抽選オブジェクトを初期化する。
rllt = WghtdRndmExtrct.new##抽選オブジェクトにワードを設定していく
for wrd in wrds##投票集計値(vote_sum_pnt)投票数(vote_cnt )から平均点を得るて、重みとする。
if wrd.vote_sum_pnt != nil || wrd.vote_cnt != nil then
rllt.addElmnt(( wrd.vote_sum_pnt / wrd.vote_cnt ).to_i + 1, wrd.id)
else
rllt.addElmnt(1, wrd.id)
endend
##バージョンを更新する
rllt_vrsn = tdy
end##ユーザーIDと本日日付の数字を元に抽選IDを得る
return rllt.getRlltValue(usrId,tdy.to_i)end
あるユーザーにとって、ある日同じものが表示されてほしいという用件だったので
このような実装となりましたが
毎回ことなる結果を表示したいのであれば
引数に乱数などを設定するとよいのではと思います。