-
Notifications
You must be signed in to change notification settings - Fork 0
528. Random Pick with Weight #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
68402a6
c6bc149
d0efc04
aec02d1
7a1f10a
76a112c
6643657
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,264 @@ | ||
| # 528. Random Pick with Weight | ||
|
|
||
| * 703. Kth Larget Element in a Streamの関連問題がDiscordにあったので、 | ||
| Arai60の途中で解いてみました。 | ||
|
|
||
| ## STEP1 | ||
|
|
||
| * 発想 | ||
| * 重み付きの確率を行う方法。 | ||
| * `w_0`, `w_1`, `w_2`, ...をsumで割り算し`w_0`/sum, `w_1`/sum, `w_2`/sum, ...とした後に、累積和をとる。 | ||
| 0から1のランダムな値がどの重みに属するかを調べることで、重みを考慮したサンプリングできそう。 | ||
|
|
||
| * 詰まったところ | ||
| * `for (const i in w)` というfor in 文を使った際に、iにstringが帰ってくることを忘れており、 | ||
| numberを返すべきところをstringで返してしまっていた。 | ||
|
|
||
| ### 線形探索を用いた方法 | ||
|
|
||
| ```javascript | ||
| const Solution = function(w) { | ||
| this.cum_sum_weights = [0] | ||
| let sum = 0 | ||
| for (const weight_i of w) { | ||
| sum += weight_i | ||
| } | ||
| for (let i=0; i<w.length; i++) { | ||
| n const weight_i = w[i] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 謎の n。これを置き直してもあまりいいことないでしょう。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wという変数が重み( |
||
| this.cum_sum_weights.push(this.cum_sum_weights[Number(i)] + weight_i / sum) | ||
| } | ||
| }; | ||
|
|
||
| Solution.prototype.pickIndex = function() { | ||
| const random_val = Math.random() | ||
| for (let i=1; i<=this.cum_sum_weights.length; i++) { | ||
| const cum_sum_weight_i = this.cum_sum_weights[i] | ||
| if (random_val <= cum_sum_weight_i) { | ||
| return i-1 | ||
| } | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| ### 二分探索を用いた方法 | ||
|
|
||
| ```javascript | ||
| const Solution = function(w) { | ||
| let sum = 0 | ||
| for (const weight_i of w) { | ||
| sum += weight_i | ||
| } | ||
| this.cum_sum_weights = [] | ||
| this.cum_sum_weights.push(0) | ||
| for (let i=0; i<w.length; i++) { | ||
| const weight_i = w[i] | ||
| const ith_cum_sum_weight = this.cum_sum_weights[i] | ||
| this.cum_sum_weights.push(ith_cum_sum_weight + weight_i / sum) | ||
| } | ||
| }; | ||
|
|
||
| // 見つけたいターゲット: | ||
| // i-1番目の累積和 < random_value <= i番目の累積和となるiの値 | ||
| // while文の引き継ぎの条件: | ||
| // left以上で、right以下であること。 | ||
| Solution.prototype.pickIndex = function() { | ||
| const random_val = Math.random() | ||
| let left = 1 | ||
| let right = this.cum_sum_weights.length - 1 | ||
| while (left < right) { | ||
| const middle = Math.floor(left + (right - left) / 2) | ||
| if (random_val <= this.cum_sum_weights[middle]) { | ||
| right = middle | ||
| } else { | ||
| left = middle + 1 | ||
| } | ||
| } | ||
| return left - 1 | ||
| }; | ||
| ``` | ||
|
|
||
|
|
||
| ## STEP2 | ||
|
|
||
| * 線形探索を用いた方法 | ||
| * `this.cum_sum_weights` が使用する直前で宣言する形に変更。sumを求める際には使用しないため。 | ||
|
|
||
| ```javascript | ||
| const Solution = function(w) { | ||
| let sum = 0 | ||
| for (const weight_i of w) { | ||
| sum += weight_i | ||
| } | ||
| this.cum_sum_weights = [] | ||
| // 番兵として、0を入れておく。 | ||
| this.cum_sum_weights.push(0) | ||
| for (let i=0; i<w.length; i++) { | ||
| const weight_i = w[i] | ||
| const ith_cum_sum_weight = this.cum_sum_weights[i] | ||
| this.cum_sum_weights.push(ith_cum_sum_weight + weight_i / sum) | ||
| } | ||
| }; | ||
|
|
||
| Solution.prototype.pickIndex = function() { | ||
| const random_val = Math.random() | ||
| for (let i=1; i<=this.cum_sum_weights.length; i++) { | ||
| // ith cumulative sum of weight. | ||
| const ith_cum_sum_weight = this.cum_sum_weights[i] | ||
| if (random_val <= ith_cum_sum_weight) { | ||
| return i-1 | ||
| } | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| ## STEP3 | ||
|
|
||
| * 二分探索を用いた方法でやってみる。 | ||
|
|
||
| ```javascript | ||
| const Solution = function(w) { | ||
| let sum = 0 | ||
| for (const weight_i of w) { | ||
| sum += weight_i | ||
| } | ||
|
|
||
| this.cum_sum_weights = [] | ||
| this.cum_sum_weights.push(0) | ||
| for (let i=0; i<w.length; i++) { | ||
| const ith_cum_sum_weight = this.cum_sum_weights[i] | ||
| this.cum_sum_weights.push(ith_cum_sum_weight + w[i] / sum) | ||
| } | ||
| }; | ||
|
|
||
| // Target: | ||
| // i-1 < target <= i | ||
| // while statement condition: | ||
| // left <= ith index <= right | ||
| Solution.prototype.pickIndex = function() { | ||
| let left = 0 | ||
| let right = this.cum_sum_weights.length - 1 | ||
| const random_val = Math.random() | ||
| while (left < right) { | ||
| const middle = left + Math.floor((right - left) / 2) | ||
| if (random_val <= this.cum_sum_weights[middle]) { | ||
| right = middle | ||
| } else { | ||
| left = middle + 1 | ||
| } | ||
| } | ||
| return left - 1 | ||
| }; | ||
| ``` | ||
|
|
||
| ## 感想 | ||
|
|
||
| * 二分探索において、ターゲットと引き継ぎ条件を考えることで、 | ||
| left,rightの設定や等号の有無について迷わなくなった。 | ||
|
|
||
| * 前のレビュー依頼から時間が空いてしまったので、もう一度習慣化する。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. そうですね。1週間休んでも終わるのが1週間遅れるだけですが、頻度が半分になると時間が倍になりますからね。特に問題ないので進みましょう。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. はい、自分ができるペースで、頻度を意識してコーディング練習会に臨もうと思います。 |
||
|
|
||
| ## その他の解法 | ||
|
|
||
| * 二分探索のターゲットを変更する。 | ||
| * 見つけたいターゲット: i-1番目の累積和 <= `random_value` < i番目の累積和となるiの値 (先ほどは、 i-1 < `random_value` <= iで等号の場所を変更した) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 等号はこっちのほうが自然でしょうね。Math.random は 0 は入って 1 は入らないので。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
と理解しました。 |
||
| * while文の引き継ぎの条件: ターゲットはleft以上で、right以下であること。 | ||
|
|
||
| ```javascript | ||
| const Solution = function(w) { | ||
| let sum = 0 | ||
| for (const weight_i of w) { | ||
| sum += weight_i | ||
| } | ||
|
|
||
| this.cum_sum_weights = [] | ||
| this.cum_sum_weights.push(0) | ||
| for (let i=0; i<w.length; i++) { | ||
| const ith_cum_sum_weight = this.cum_sum_weights[i] | ||
| this.cum_sum_weights.push(ith_cum_sum_weight + w[i] / sum) | ||
| } | ||
| }; | ||
| Solution.prototype.pickIndex = function() { | ||
| const random_val = Math.random() | ||
| let left = 1 | ||
| let right = this.cum_sum_weights.length - 1 | ||
| while (left < right) { | ||
| const middle = Math.floor(left + (right - left) / 2) | ||
| if (random_val < this.cum_sum_weights[middle]) { | ||
| right = middle | ||
| } else { | ||
| left = middle + 1 | ||
| } | ||
| } | ||
| return left - 1 | ||
| }; | ||
| ``` | ||
|
|
||
| ## レビューを受けて | ||
|
|
||
| ### レビュー (1) 浮動小数点と整数の違い | ||
|
|
||
| ```javascript | ||
| const Solution = function(w) { | ||
| let sum = 0 | ||
| for (const weight_i of w) { | ||
| sum += weight_i | ||
| } | ||
|
|
||
| this.cum_sum_weights = [] | ||
| this.cum_sum_weights.push(0) | ||
| for (let i=0; i<w.length; i++) { | ||
| const ith_cum_sum_weight = this.cum_sum_weights[i] | ||
| this.cum_sum_weights.push(ith_cum_sum_weight + w[i] / sum) | ||
| } | ||
| }; | ||
|
|
||
| // Target: | ||
| // i-1 < target <= i | ||
| // while statement condition: | ||
| // left <= ith index <= right | ||
| Solution.prototype.pickIndex = function() { | ||
| let left = 0 | ||
| let right = this.cum_sum_weights.length - 1 | ||
| const random_val = Math.random() | ||
| while (left < right) { | ||
| const middle = left + Math.floor((right - left) / 2) | ||
| if (random_val <= this.cum_sum_weights[middle]) { | ||
| right = middle | ||
| } else { | ||
| left = middle + 1 | ||
| } | ||
| } | ||
| return left - 1 | ||
| }; | ||
| ``` | ||
|
|
||
| * レビューコメントは、浮動小数点は副作用があるので、整数で解いた方が好み。 | ||
| * 参考 : https://github.com/shintaroyoshida20/leetcode/pull/16#discussion_r2072320084 | ||
|
|
||
| ```javascript | ||
| const Solution = function(w) { | ||
| this.cum_sum_weights = [0] | ||
| let sum_weight = 0 | ||
| for (let i = 0; i < w.length; i++) { | ||
| this.cum_sum_weights[i + 1] = this.cum_sum_weights[i] + w[i] | ||
| } | ||
| }; | ||
|
|
||
| Solution.prototype.pickIndex = function() { | ||
| const size = this.cum_sum_weights.length | ||
| const sum = this.cum_sum_weights[size - 1] | ||
|
|
||
| // 0 から sum - 1の値をランダムに作成する。 | ||
| const random_val = Math.floor(Math.random() * sum) | ||
|
|
||
| // i-1 <= random_val < iとなるiを探す。 | ||
| // 言い換えると、random_valより大きいiで最小のiを探す。 | ||
| for (let i = 1; i <= size; i++) { | ||
| const ith_cum_sum_weight = this.cum_sum_weights[i] | ||
| if (random_val < ith_cum_sum_weight) { | ||
| return i - 1 | ||
| } | ||
| } | ||
| throw new Error("unexpected behavior is occurred.") | ||
| }; | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
確率だからよいかと思う一方で、浮動小数点は色々なことが起きるので、自然数で処理したほうが私は好みです。
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
を意識ができておりませんでした。ご指摘、ありがとうございます。
懸念項目の一例を調べました。
参考:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
6643657 で整数で解く方法を追加しました。