如何在std :: set中选择一个随机元素?

时间:2010-06-16 11:26:11

标签: c++ iterator set

如何在std::set中选择随机元素?

我天真地尝试过这个:

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

但是operator+不允许这样做。

6 个答案:

答案 0 :(得分:42)

您可以使用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);

  set<int>::const_iterator it(s.begin());

  // 'advance' the iterator 5 times
  advance(it,5);
}

答案 1 :(得分:2)

如果随机访问很重要,并且您可以使用O(N)平均插入次数,那么this paper中给出的解决方法可能很方便。

主要思想是使用排序向量,然后查找函数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;
 }
};

对于更复杂的实现,您可能还会考虑this page

编辑:或者甚至更好,使用boost::container::flat_set,它使用上面的想法实现集合,即作为有序向量。

答案 2 :(得分:2)

第一个解决方案: O(log n)时间/ O(1)在空间中(不均匀!)

在上面的评论中假设,它可以在 O(log(n))(对于std::advance的vs O(n))中完成一个向量(使用 O(n)更多空间)使用我描述的方法here

基本上,你:

  • 检查该集合是否为空(如果是,则没有希望)
  • 生成随机值
  • 如果已经返回,则将其插入
  • 在其上获取一个迭代器it
  • 在最后*(it++)获取随机元素*(set.begin())it
  • 在删除您插入的元素之前不返回

n.b:正如 Aaron 指出的那样,元素不会随机选择统一。您需要使用与集合中的元素相同的分布来构建随机元素,以接近统一轮询。

第二种解决方案: O(1)时间/ O(n)空间(均匀)

davidhigh 已经给出了带矢量的解决方案,但是有一个问题,因为当你弹出你的堆栈的一个元素时,你将不得不在< em> O(n)或者您可以在每次要检索随机元素时重建矢量,但也可以 O(n)

要避免此问题并将插入/删除保留到 O(log n),您可以保留std::unordered_set并使用similar method来获取第一个解决方案 O(1)中的随机元素。

p.s:如果你的元素很大,你可以使用一组无序的指针(带有修改的hasher)来节省一些内存。

答案 3 :(得分:1)

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;
}

会是这样做的一种方式,虽然不是很漂亮;

答案 4 :(得分:1)

要从集合中获取随机元素,请首先使用rand()函数获取一个随机数,然后根据集合大小获取一个模数(%),以使我们的迭代器不会超出范围。现在,要获得随机元素,只需对idx = rand()%s.size()进行迭代次数即可获得随机元素。这种方法中每个元素都有相同的发生概率。

// making set
unordered_set<int> s;
s.insert(1);
s.insert(2);
s.insert(3);
s.insert(4);

// logic
int idx = rand()%s.size();
auto it = s.begin();
for (int i = 0; i < idx; i++)
{
    it++;
}
return *it;

答案 5 :(得分:0)

C ++ 17 $query = 'SELECT * FROM user WHERE 1 = 1 '; if(!empty($serviceNumber) ) { $query .= ' and serviceNumber = ? '; $params[] = $serviceNumber; } if(!empty($name)) { $query .= ' and userName = ? '; $params[] = $name; } if(!empty($status)) { $query .= ' and status = ? '; $params[] =$status; } if(!empty($params)) { $sth->execute($params); $dbh->prepare($query); $result= mysqli_query($dbcon,$sth); if(!$result ) { die('Could not get data: ' . mysqli_error()); } } else { echo 'Missing Values'; }

这将是一种方便的,但效率不高的(O(n))方法:

std::sample

但我认为,为了提高效率,您只需复制到另一种类型的结构:How to select a random element in std::set in less than O(n) time?