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 ?
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 ?
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()) );
This is not an O(1) solution (unless you got only one edge):
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.
you can see this problem:
problem 380. Insert Delete GetRandom O(1) you can build a vector to use vector random iterators, get random values more efficiently. Like this:
class RandomizedSet {
public:
unordered_map<int, int> m;
vector<int> data;
RandomizedSet() {
}
bool insert(int val) {
if(m.count(val)){
return false;
} else{
int index = data.size();
data.push_back(val);
m[val] = index;
return true;
}
}
bool remove(int val) {
if(m.count(val)){
int curr_index = m[val];
int max_index = data.size()-1;
m[data[max_index]] = curr_index;
swap(data[curr_index], data[max_index]);
data.pop_back();
m.erase(val);
return true;
} else{
return false;
}
}
int getRandom() {
return data[rand() % data.size()];
}
};
/**
* Your RandomizedSet object will be instantiated and called as such:
* RandomizedSet* obj = new RandomizedSet();
* bool param_1 = obj->insert(val);
* bool param_2 = obj->remove(val);
* int param_3 = obj->getRandom();
*/
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:
std::vector<std::string> vec
int index
ranging from 0
to vec.size() - 1
edges[vec[index]]
Strict O(1) solution without buckets:
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 :)
Maybe we can...
#include <iostream>
#include <ctime>
#include <unordered_map>
struct Edge{
int x;
int y;
};
using EdgeMap = std::unordered_map<std::string, Edge>;
EdgeMap edges;
EdgeMap::iterator make_random_iterator(EdgeMap *map) {
// make sure that random_key is not conflict with existing keys
std::string random_key = "mock_key:" + std::to_string(std::rand());
EdgeMap::iterator iter =
map->insert(std::make_pair(random_key, Edge())).first;
//now we got an random iterator
iter = map->erase(iter);
return iter;
}
int main(int, char**){
std::srand(std::time(nullptr));
//insert some keys
for (int i=0;i<1000;i++) {
edges[std::to_string(std::rand())] = Edge();
}
//visit 10 keys randomly
for (int i=0;i<10;i++) {
auto iter = make_random_iterator(&edges);
std::cout << "i:" << i << " pick_key:" << iter->first << std::endl;
}
return 0;
}
If by efficient you mean O(1), then no, it is not possible.
Since the iterators returned by
unordered_map::begin / end
areForwardIterator
s, the approaches that simply usestd::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.
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.