问题
I define an unordered_map
like this:
std::unordered_map<std::string, Edge> edges;
Is there a efficient way to choose a random Edge from the unordered_map edges ?
回答1:
Pre-C++11 solution:
std::tr1::unordered_map<std::string, Edge> edges;
std::tr1::unordered_map<std::string, Edge>::iterator random_it = edges.begin();
std::advance(random_it, rand_between(0, edges.size()));
C++11 onward solution:
std::unordered_map<std::string, Edge> edges;
auto random_it = std::next(std::begin(edges), rand_between(0, edges.size()));
The function that selects a valid random number is up to your choice, but be sure it returns a number in range [0 ; edges.size() - 1]
when edges
is not empty.
The std::next function simply wraps the std::advance function in a way that permits direct assignation.
回答2:
Is there a efficient way to choose a random Edge from the unordered_map edges ?
If by efficient you mean O(1), then no, it is not possible.
Since the iterators returned by unordered_map::begin / end
are ForwardIterator
s, the approaches that simply use std::advance
are O(n) in the number of elements.
If your specific use allows it, you can trade some randomness for efficiency:
You can select a random bucket (that can be accessed in O(1)), and then a random element inside that bucket.
int bucket, bucket_size;
do
{
bucket = rnd(edges.bucket_count());
}
while ( (bucket_size = edges.bucket_size(bucket)) == 0 );
auto element = std::next(edges.begin(bucket), rnd(bucket_size));
Where rnd(n)
returns a random number in the [0,n) range.
In practice if you have a decent hash most of the buckets will contain exactly one element, otherwise this function will slightly privilege the elements that are alone in their buckets.
回答3:
This is how you can get random element from a map:
std::unordered_map<std::string, Edge> edges;
iterator item = edges.begin();
int random_index = rand() % edges.size();
std::advance(item, random_index);
Or take a look at this answer, which provides the following solution:
std::unordered_map<std::string, Edge> edges;
iterator item = edges.begin();
std::advance( item, random_0_to_n(edges.size()) );
回答4:
Strict O(1) solution without buckets:
- Keep a vector of keys, when you need to get a random element from your map, select a random key from the vector and return corresponding value from the map - takes constant time
- If you insert a key-value pair into your map, check if such key is already present, and if it's not the case, add that key to your key vector - takes constant time
- If you want to remove an element from the map after it was selected, swap the key you selected with the back() element of your key vector and call pop_back(), after that erase the element from the map and return the value - takes constant time
However, there is a limitation: if you want to delete elements from the map aside from random picking, you need to fix your key vector, this takes O(n) with naive approach. But still there is a way to get O(1) performance: keep a map that tells you where the key is in the key vector and update it with swap :)
回答5:
The solution of
std::unordered_map<std::string, Edge> edges;
auto random_it = std::next(std::begin(edges), rand_between(0, edges.size()));
is extremely slow....
A much faster solution will be:
- when assigning edges, simutaneously emplaces its keys to
std::vector<std::string> vec
- random an
int index
ranging from0
tovec.size() - 1
- then get
edges[vec[index]]
来源:https://stackoverflow.com/questions/27024269/select-random-element-in-an-unordered-map