How can I select a random element in an std::set
?
I naively tried this:
int GetSample(const std::set<int>& s) {
double r = rand() % s.size();
return *(s.begin() + r); // compile error
}
But the operator+
is not allowed in this way.
How can I select a random element in an std::set
?
I naively tried this:
int GetSample(const std::set<int>& s) {
double r = rand() % s.size();
return *(s.begin() + r); // compile error
}
But the operator+
is not allowed in this way.
would be one way of doing it, although not pretty;
You could use the
std::advance
method.C++17
std::sample
This will be a convenient, although not very efficient (O(n)) method:
But I think that for efficiency you just need to copy to another type of structure: How to select a random element in std::set in less than O(n) time?
First Solution : O(log n) in time / O(1) in space (not uniform !)
A hypothesized in a comment above, it can be done in O(log(n)) (vs O(n) for
std::advance
) without a vector (using O(n) more space) by using the method I describe here.Essentially, you :
it
on it*(it++)
or*(set.begin())
ifit
at the endn.b : As pointed out by Aaron the element is not chosen uniformly at random. You need to build the random element with the same distribution than the elements in the set to approach a uniform polling.
Second Solution : O(1) in time / O(n) in space (uniform)
davidhigh already gave the solution with a vector but there is a problem because when you pop an element of your stack, you will have to perform a linear search in O(n) or you can rebuild your vector each time you want to retrieve a random element but that is O(n) too.
To avoid this problem and keep the insert/delete to O(log n), you can keep an
std::unordered_set
and use a similar method to the first solution to get a random element in O(1).p.s : If your elements are large you can use an unordered set of pointers (with a modified hasher) to spare some memory.
If the random access is important and you can live with O(N) average effort for the insertion, then the workaround given in this paper might be convenient.
The main idea there is to use a sorted vector, and then for lookup the function
std::lower_bound
. This, the lookup takes O(log N) just as in a normal set. Further, (random) insertion takes O(N), as all following elements must be shifted just like in a normal vector (and possibly a reallocation is performed). Insertion at the back, however, is constant (except for the reallocation. You can avoid this by callingreserve()
with a large enough storage).Finally, the main point of the question: Random access is O(1). Just draw a random number
i
from a uniform distribution in[0, V.size()-1]
, and return the corresponding elementV[i]
.Here is the code basis out of the paper, which implements this sorted vector. Extend it as needed:
For a more sophisticated implementation, you might also consider this page.
EDIT: or even better, use
boost::container::flat_set
, which implements the set using the idea above, i.e. as a sorted vector.