---
# System prepended metadata

title: 'Ruby の Issue #4247 について'

---

# Ruby の [Issue #4247](https://bugs.ruby-lang.org/issues/4247) について

## `Array#sample` の機能拡張に関する個人的な好みについて

Ruby のコア API として提供することを考えると次の2つの案のどちらが良いと思う

1. `sample` に統一する
    - `sample` と `choice` に分けるよりは `sample` に統一する方がシンプルだと考えた
    - 重みつき抽出に対応するなら `weights` キーワード引数で相対重みの配列を受け取る
        - 重みつき非復元抽出に対応するかどうかは要検討
            - アルゴリズムは [[Efraimidis 2015](https://arxiv.org/abs/1012.0256)] にある
        - n=1 の場合の重みつき抽出に対応するかどうかも要検討 (1個だけのためにエイリアス法のテーブルを作って捨てることになる)
    - 復元抽出と非復元抽出の切り替えは `replace` キーワード引数の値を見る
        - `replace` と言う用語はランダムサンプリングにおいては定着している用語なのでそのままで良い
        - Issue では `repeat` が代わりに使われているが、`repeat` はただの繰り返しを意味していて、母集団の復元/非復元を指定する単語としては不適切だと思う
        - さっき遠藤さんと議論中にでてきた `rep` という省略形は良いと思った
2. `sample` と `choice` に分ける
    - `replace` と言う用語を採用することに強い抵抗がある場合はこっち
    - 既存の `sample` が非復元抽出なので、 `choice` は復元抽出とする
    - `weights` キーワード引数で相対重み配列を受け付ける

## もう一つの新しい案

- 非復元抽出と重み付き抽出に対応した高機能 `sample` を `Random` のクラスメソッドとして提供する: `Random.sample(seq, weights: nil, replace: false)`
    - `Random` クラスの関数なら `replace` でも違和感はないのでは？
    - これを導入できるなら `Array#sample` は機能拡張しなくて良いと思う
- 重み付き抽出を繰り返し行うが事前に回数がわからない場合に対応するために `Random::Sampler` クラスを導入する
    ```rb
    sampler = Random::Sampler.new([:a, :b, :c], weights: [1, 2, 3], replace: true)
    sampler.take(10) #=> [:c, :b, :c, :a, :c, :c, :c, :a, :b, :c]
    ```
- おまけ: `rand` 関数の拡張案
    - `rand` 関数が複数の乱数を一度に生成できれば、重みなし復元抽出を次のように書ける
        - 例えば配列 `ary` から100個を復元抽出する場合は `ary.values_at(*rand(ary.length, 100))`

## 参考資料: 他の言語の状況を調査した

- R
    - `sample(ary, size, replace=FALSE, prob=NULL)`
- Julia
    - `sample([rng], ary, [weights], size; replace=true, ordered=false)` が StatBase ライブラリで提供されている
- Python
    - 1個だけ抽出する `choice` 、複数個を非復元抽出する `sample` 、複数個を復元抽出する `choices` の3種類が `random` モジュールで提供されている
    - `choices` には重み配列 (もしくは累積重み配列) を渡すと重みつき抽出になる
    - `sample` は重みに対応していない
- Rust
    - 1個だけ抽出する `choose` 、複数個を非復元抽出する `choose_multiple` が `rand::seq::IndexedRandom` trait で提供されている。
    - 1個だけ重みつき抽出する `choose_weighted` 、複数個を重みつき復元抽出する `choose_weighted_multiple` が `rand::seq::IndexedRandom` trait で提供されている。
        - 複数個を重みなしで復元抽出する時は `choose_weighted_multiple` で全て同じ重みを返す関数を weight として与えるっぽい
        - 重み関数にはインデックスではなく要素そのものが渡される
            - 同じ要素が複数入ってるシーケンスに対する抽出の場合、Python の `choices` のような振る舞いをさせることはできない

