Select random element in an unordered_map

前端 未结 5 1074
北海茫月
北海茫月 2021-02-04 05:50

I define an unordered_map like this:

std::unordered_map edges;

Is there a efficient way to choose a rando

相关标签:
5条回答
  • 2021-02-04 06:21

    Strict O(1) solution without buckets:

    1. 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
    2. 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
    3. 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 :)

    0 讨论(0)
  • 2021-02-04 06:21

    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 from 0 to vec.size() - 1
    • then get edges[vec[index]]
    0 讨论(0)
  • 2021-02-04 06:35

    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.

    0 讨论(0)
  • 2021-02-04 06:39

    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 ForwardIterators, 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.

    0 讨论(0)
  • 2021-02-04 06:41

    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()) );
    
    0 讨论(0)
提交回复
热议问题