web-dev-qa-db-ja.com

std :: setでランダムな要素を選択する方法は?

std::setでランダムな要素を選択するにはどうすればよいですか?

私は単純にこれを試しました:

int GetSample(const std::set<int>& s) {
  double r = Rand() % s.size();
  return *(s.begin() + r); // compile error
}

ただし、operator+はこの方法では使用できません。

30
Frank

std::advance 方法。

#include <set>
#include <algorithm>

int main() {
  using namespace std;
  // generate a set...
  set<int> s;
  for( int i = 0; i != 10; ++i ) s.insert(i);
  auto r = Rand() % s.size(); // not _really_ random
  auto n = *select_random(s, r);
}

どこ

template<typename S>
auto select_random(const S &s, size_t n) {
  auto it = std::begin(s);
  // 'advance' the iterator n times
  std::advance(it,n);
  return it;
}
44
xtofl

最初のソリューション: O(log n) 時間内に/ O(1) 空間内(均一ではない!)

上記のコメントで仮説を立てると、ベクトルなしでO(log(n))(vs O(n) for _std::advance_)で実行できます( O(n)より多くのスペースを使用)私が説明する方法を使用して ここに

基本的に、あなた:

  • セットが空かどうかを確認します(空の場合、希望はありません)。
  • ランダムな値を生成する
  • すでにそこにある場合はそれを返し、それ以外の場合は挿入する
  • イテレータitを1つ取得します
  • 最後にitの場合、ランダム要素を*(it++)または*(set.begin())として取得します
  • 挿入した要素を削除する前に返さない

n.b:Aaronで指摘されているように、要素は選択されません一様にランダムに。均一なポーリングにアプローチするには、セット内の要素と同じ分布を持つランダム要素を構築する必要があります。

2番目のソリューション: O(1) 時間内に/ O(n) 空間内(均一)

davidhighはすでにベクトルで解を与えていますが、問題があるのはpopスタックの要素である場合、で線形検索を実行する必要があるためです。 O(n)または、ランダムな要素を取得するたびにベクトルを再構築できますが、これもO(n)です。

この問題を回避し、挿入/削除をO(log n)に維持するには、_std::unordered_set_を維持し、 同様の方法を使用できますO(1)でランダムな要素を取得する最初のソリューションに。

ps:要素が大きい場合は、順序付けされていないポインタのセットを(変更されたハッシュで)使用して、メモリを節約できます。

2
matovitch

ランダムアクセスが重要であり、O(N)挿入の平均労力を十分に活用できる場合は、 このペーパー で示されている回避策が便利です。

主なアイデアは、ソートされたベクトルを使用し、次に関数_std::lower_bound_を検索することです。これは、通常のセットの場合と同様に、ルックアップはO(log N)を取ります。さらに、(ランダムな)挿入にはO(N)が必要です。これは、後続のすべての要素を法線ベクトルと同じようにシフトする必要があるためです(再割り当てが実行される可能性があります)。ただし、背面への挿入は一定です(再割り当てを除きます。十分な大きさのストレージを指定してreserve()を呼び出すと、これを回避できます)。

最後に、質問の要点:ランダムアクセスはO(1)です。一様分布から乱数iを描画するだけです[0, V.size()-1]で、対応する要素_V[i]_を返します。

これは、このソートされたベクトルを実装する、ペーパーからのコードの基礎です。必要に応じて拡張します。

_template <class T, class Compare = std::less<T> >
struct sorted_vector {
 using std::vector;
 using std::lower_bound;
 vector<T> V;
 Compare cmp; 
 typedef typename vector<T>::iterator iterator;
 typedef typename vector<T>::const_iterator const_iterator;
 iterator begin() { return V.begin(); }
 iterator end() { return V.end(); }
 const_iterator begin() const { return V.begin(); }
 const_iterator end() const { return V.end(); }

 //...if needed, implement more by yourself

 sorted_vector(const Compare& c = Compare()) : V(), cmp(c) {}
 template <class InputIterator>
 sorted_vector(InputIterator first, InputIterator last, Const Compare& c = Compare())
 : V(first, last), cmp(c)
 {
 std::sort(begin(), end(), cmp);
 }

 //...

 iterator insert(const T& t) {
     iterator i = lower_bound(begin(), end(), t, cmp);
     if (i == end() || cmp(t, *i))
        V.insert(i, t);
      return i;
 }
 const_iterator find(const T& t) const {
     const_iterator i = lower_bound(begin(), end(), t, cmp);
      return i == end() || cmp(t, *i) ? end() : i;
 }
};
_

より洗練された実装の場合は、 このページ を検討することもできます。

編集:またはさらに良いことに、_boost::container::flat_set_を使用します。これは、上記のアイデアを使用してセットを実装します(つまり、ソートされたベクトルとして)。

2
davidhigh
int GetSample(const std::set<int>& s) {
  double r = Rand() % s.size();
  std::set<int>::iterator it = s.begin();
  for (; r != 0; r--) it++;
  return *it;
}

きれいではありませんが、それを行う1つの方法です。

1
Amir Rachum

C++ 17 std::sample

これは便利ですが、あまり効率的ではありません(O(n))メソッドです。

#include <algorithm>
#include <iostream>
#include <random>
#include <set>
#include <vector>

int main() {
    std::set<int> in{1, 2, 3, 5, 7};
    std::vector<int> out;
    std::sample(in.begin(), in.end(), std::back_inserter(out),
                3, std::mt19937{std::random_device{}()});
    for (auto i : out)
        std::cout << i << std::endl;
}

しかし、私は効率のために別のタイプの構造にコピーする必要があるだけだと思います: O(n)時間未満でstd :: setでランダムな要素を選択する方法?