🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

Q&A

解決済

4回答

3975閲覧

重複無しでランダムサンプリングする方法がわかりません

mmtaro000

総合スコア15

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

配列

配列は、各データの要素(値または変数)が連続的に並べられたデータ構造です。各配列は添え字(INDEX)で識別されています。

0グッド

0クリップ

投稿2020/12/02 05:01

c++で、予め用意した配列からランダムで任意の個数抽出するという処理を行いたいのですが、どうすればよいでしょうか?例えば、100個の要素がある配列から10個の要素をランダムに抽出する、という感じです。
個人で考えた方法として、vector形式に保存して毎回shuffle関数で要素の並びをシャッフルし、先頭n個を抽出する、という方法を考えているのですが、shuffle関数が再現性のある処理なのかわからず、適しているかわからないのが現状です。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

こんなんでどーぢゃろ

C++

1#include <random> 2#include <iterator> 3#include <algorithm> 4 5/* 6 [first, last) の中からでたらめにひとつ選び、 7 末尾要素と入れ替える 8*/ 9template<typename Iterator, typename Engine> 10void pop_random(Iterator first, Iterator last, Engine& eng) { 11 std::uniform_int_distribution<int> dist(0, std::distance(first,last)-1); 12 std::iter_swap(std::next(first, dist(eng)), std::prev(last)); 13} 14 15#include <iostream> 16#include <vector> 17#include <numeric> 18 19int main() { 20 std::vector<int> data(100); 21 std::iota(data.begin(), data.end(), 0); 22 23 std::mt19937 eng; 24 for ( int i = 0; i < 10; ++i ) { 25 pop_random(data.begin(), data.end(), eng); 26 std::cout << data.back() << std::endl; 27 data.pop_back(); // 選んだ要素を取り除く 28 } 29}

投稿2020/12/02 12:55

episteme

総合スコア16612

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yumetodo

2020/12/02 13:40 編集

それまったく一様じゃない乱数ですよね。0から99までの連番から10回ランダムに値を抜き取って末尾要素と入れ替えた残りがどうなってるか考えてください。 私の回答の「C++で効率よく重複のない乱数列を生成する - Qiita」のコメント欄見てくるともっといろいろ注意を払う必要があることがあることがわかると思います。
episteme

2020/12/02 13:50

一様じゃない? でたらめにひとつえらんで袋から取り出したことにならない、と?
Zuishin

2020/12/04 12:45 編集

> 0から99までの連番から10回ランダムに値を抜き取って末尾要素と入れ替えた残りがどうなってるか考えてください。 どうなるんでしょう? 欲しいのは 10 個だけなので、残りの並びがどうなっていようと問題ないはずですが。 フィッシャー–イェーツのシャッフルアルゴリズムの一部ですよね? [フィッシャー–イェーツのシャッフル - Wikipedia] https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%83%E3%82%B7%E3%83%A3%E3%83%BC%E2%80%93%E3%82%A4%E3%82%A7%E3%83%BC%E3%83%84%E3%81%AE%E3%82%B7%E3%83%A3%E3%83%83%E3%83%95%E3%83%AB > 1. 1 から N までの数字を書く。 > 2. まだ消されてない数字の数を数え、1 からその数以下までのランダムな数字 k を選ぶ。 > 3. 残っている数字から k 番目の数字を消し、別の場所にその数字を書き出す。
yumetodo

2020/12/04 13:47

distributionがつねに0からになってるからまずいような印象を受けたんですが、大丈夫なのか・・・?混乱してきた
yominet

2020/12/04 16:30

distributionで発生させてるのは「最終的に欲しい数値」ではなく、dataから取り出したい個所 流れを書くと dist[0~99]の中から1個選ぶ 選んだ番号k1をつかってdata[k1]を操作する(後ろといれかえ、表示、そして削除) dist[0~98]の中から1個選ぶ。 選んだ番号k2をつかってdata[k2]を操作する(同) (続く) の繰り返しだからdistは0から始まらないといけないのでは
yumetodo

2020/12/06 08:05

あー、なるほど
guest

0

ベストアンサー

昔ドンピシャな内容について論じたことがあります。unordered_setを使ってる例は除外するとして、再現性はいずれもseedにのみ依存します。

投稿2020/12/02 07:50

編集2020/12/02 07:52
yumetodo

総合スコア5852

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mmtaro000

2020/12/02 07:52

ありがとうございます。参考にさせていただきます。
yumetodo

2020/12/02 08:07

後者の記事のコメント欄では、かなり数学的で私もついていけてない議論が展開されています。数学力に自信があるならそのへんも参考になるかと思います。
guest

0

非復元抽出は C++17 以前であれば、「シャッフル → 先頭 n 個を取り出す」とすればいいのではないでしょうか。

shuffle - cpprefjp C++日本語リファレンス

C++17 では std::sample() を使えば非復元抽出が行えます。

cpp

1#include <algorithm> 2#include <iostream> 3#include <numeric> 4#include <random> 5#include <vector> 6 7static std::mt19937 engine(0); // シードを0で固定 8 9std::vector<int> sample(const std::vector<int> &v, size_t n) 10{ 11 if (v.size() < n) 12 n = v.size(); 13 14 // シャッフルする。 15 std::vector<int> v2(v); 16 std::shuffle(v2.begin(), v2.end(), engine); 17 18 // 先頭 n 個を取り出す。 19 std::vector<int> out(v2.begin(), v2.begin() + n); 20 21 return out; 22} 23 24int main() 25{ 26 std::vector<int> v(100); 27 std::iota(v.begin(), v.end(), 0); 28 29 // C++17未満 30 { 31 std::vector<int> out = sample(v, 10); 32 33 std::for_each(out.begin(), out.end(), [](int x) { std::cout << x << " "; }); 34 std::cout << std::endl; 35 } 36 37 // C++17 38 { 39 std::vector<int> out; 40 std::sample(v.begin(), v.end(), std::back_inserter(out), 10, engine); 41 42 std::for_each(out.begin(), out.end(), [](int x) { std::cout << x << " "; }); 43 std::cout << std::endl; 44 } 45} 46

shuffle関数が再現性のある処理なのかわからず

シャッフルも乱数生成器を利用しているので、乱数生成器のシードを固定すれば再現性は確保できます。

投稿2020/12/02 06:29

tiitoi

総合スコア21956

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mmtaro000

2020/12/02 07:53

10000個のうちから抽出するので計算時間もそこまでかからなさそうですね。使用してみます。ありがとうございます。
guest

0

配列から選んで、重複していたら選び直し、をすればいいだけでしょ。

#shuffle関数でいいんじゃないかと。

投稿2020/12/02 06:02

y_waiwai

総合スコア88038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mmtaro000

2020/12/02 08:23

それが実装簡単そうなのでそうしてみます。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問