#ifndef HAT_H #define HAT_H /* ================================================================================ hat container class version 1.59 by AngleWyrm The hat is a container that provides storage and retrieval from a weighted random set, both with and without replacement. Items contained can have unequally distributed probabilities, and also don't have to be unique. The probability of an object being selected is weight/totalChances, where totalChances is the sum of the weights assigned to the objects in the hat. Optionally, weights may be specified as fractions using the _normalized() functions. An user-specified random number generator object may be passed to the hat: hat names( my_rng_instance ); If not specified, std::rand() is used as the random number generator. INTERFACE: put(obj, wt) stores an object in hat get() examines a randomly selected object (with replacement) peek() returns an iterator to a random object pull(buf) removes a randomly selected object (without replacement) get_weight(iter) set_weight(iter, wt) total_chances() probability(iter) erase(iter) deletes referenced item from hat // functions for using fractional weights (0, 1] // user must ensure weights sum to 1.0 put_normalized(obj, float) set_weight_normalized(obj, float) get_weight_normalized(iter) ADDITIONAL PUBLICs: clear() size() begin() end() empty() swap(in) max_size() PERFORMANCE: peek(), put(), and pull() are all logarithmic time complexity, O(logN) ================================================================================ */ #include // rand #include // vector #include // list #include // out_of_range #include // unary_function #include #include // for code clarity typedef size_t index_type; typedef size_t weight_type; const float NORMALIZE = RAND_MAX; //////////////////////////////////////////////////////////////////////////////// // std::rand() unary functor class default_rng_type { public: weight_type operator()( weight_type range ) { // range validation if ( range <= 0 ) return 0; // handle cases where range doesn't evenly divide RAND_MAX, int result; int bucket_size = RAND_MAX / range; do { result = std::rand() / bucket_size; } while ( result >= range ); return result; } }; // define one (external) instance of this class for default behavior // can be called directly if desired: // unsigned int result = std_rand(range) // [0..range) extern default_rng_type std_rand; //////////////////////////////////////////////////////////////////////////////// // boost::random adaptor #ifdef BOOST_RANDOM_HPP template< class generator_type > class boost_adaptor{ public: // typedef typedef unsigned long value_type; // constructor boost_adaptor( generator_type gen ) : generator(gen){}; generator_type generator; // main operator() function value_type operator()( value_type range ) { return boost::uniform_int<>(0, range-1)(generator); } }; #endif //////////////////////////////////////////////////////////////////////////////// // hat container template > class hat { public: ///////////////////////////////////////////////////////////////// // internal iterator class class iterator{ public: T& operator*(){return *items_iter; } bool operator!=(const iterator other)const { return ( (items_iter!=other.items_iter) || (index!=other.index) );} bool operator==( const iterator &other )const { return ( (items_iter==other.items_iter) && (index==other.index) );} iterator& operator++(){ ++items_iter; ++index; return *this;} iterator operator++(int){iterator temp = *this; ++items_iter; ++index; return temp;} void operator=(typename std::list::iterator itr){ items_iter = itr; } void operator=(const iterator &i){ items_iter = i.items_iter; index = i.index; } iterator(){ index = 0; } iterator(const iterator &i): items_iter(i.items_iter), index(i.index){} iterator( typename std::list::iterator itr, index_type idx) : items_iter(itr), index(idx){} typedef typename std::list::iterator list_iterator; list_iterator items_iter; index_type index; // reverse lookup within tree vector template struct iterator_traits { typedef typename Iterator::iterator_category iterator_category; typedef typename Iterator::value_type value_type; typedef typename Iterator::difference_type difference_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; }; }; ///////////////////////////////////////////////////////////////// // internal const_iterator class class const_iterator{ public: const T& operator*() const {return *items_iter; } bool operator!=(const iterator other)const { return ( (items_iter!=other.items_iter) || (index!=other.index) );} bool operator==( const iterator &other )const { return ( (items_iter==other.items_iter) && (index==other.index) );} const_iterator& operator++(){ ++items_iter; ++index; return *this;} const_iterator operator++(int){const_iterator temp = *this; ++items_iter; ++index; return temp;} void operator=(typename std::list::iterator itr){ items_iter = itr; } void operator=(const iterator &i){ items_iter = i.items_iter; index = i.index; } const_iterator(){ index = 0; } const_iterator(const iterator &i): items_iter(i.items_iter), index(i.index){} const_iterator( typename std::list::iterator itr, index_type idx) : items_iter(itr), index(idx){} typedef typename std::list::iterator list_iterator; list_iterator items_iter; index_type index; // reverse lookup within tree vector template struct iterator_traits { typedef typename Iterator::iterator_category iterator_category; typedef typename Iterator::value_type value_type; typedef typename Iterator::difference_type difference_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; }; }; ///////////////////////////////////////////////////////////////// // public interface and additional public utility functions typedef typename std::list::value_type value_type; typedef typename std::list::size_type size_type; typedef allocator_type allocator; // modifying functions T& get(); void swap(T in) { items.swap(in); } void put( T const & in_object, const weight_type& in_weight = 1 ); void put_normalized( T const & in_object, const float & normalized_weight ); T& pull( T& ); void erase( iterator ); void set_weight( iterator, const weight_type& ); void set_weight_normalized( iterator, const float& normalized_weight ); void clear(){ tree.clear(); items.clear(); } // constant (non-modifying) queries size_t total_chances() const { return sum_chances; }; size_type size() const { return items.size(); } size_type max_size() const { return items.max_size(); } bool empty() const { return items.empty(); } weight_type get_weight( iterator i ) const{return tree[i.index].weight;}; float probability( iterator i ) const { return float( tree[i.index].weight ) / float( sum_chances ); }; float get_weight_normalized( iterator i ) const { return (float)tree[i.index].weight / NORMALIZE; }; iterator peek(); iterator begin(){ return iterator(items.begin(),0); }; iterator end(){ return iterator(items.end(), items.size()); }; weight_type get_weight( const_iterator i ) const{return tree[i.index].weight;}; float probability( const_iterator i ) const { return float( tree[i.index].weight ) / float( sum_chances ); }; float get_weight_normalized( const_iterator i ) const { return (float)tree[i.index].weight / NORMALIZE; } const_iterator begin() const { return const_iterator( items.const_begin(), 0); }; const_iterator end() const { return const_iterator( items.const_end(), 0); }; // constructors, destructor, assignment hat( random_type rng_instance = std_rand ) : random(rng_instance){ sum_chances = 0;} hat( const hat& other ); ~hat(){ clear(); } hat& operator=( const hat& ); protected: random_type random; ///////////////////////////////////////////////////////////////// // internal node class class node { public: iterator item; // points into items storage container weight_type weight; weight_type family_weight; // sum of my weight + children (if any) node( iterator in, weight_type in_weight = 1) : item(in), weight(in_weight), family_weight(in_weight){}; }; std::vector< node, allocator_type > tree; std::list items; weight_type sum_chances; index_type find_index( index_type index, weight_type target ); void update_weights( index_type index ); }; //////////////////////////////////////////////////////////////////////////////// // put template void hat::put( T const & in, const weight_type& in_weight ) { items.push_back( in ); iterator iter( --items.end(), items.size()-1); weight_type weight = in_weight; if(weight < 1) weight = 1; sum_chances += weight; node new_node( iter, weight ); tree.push_back( new_node ); update_weights( tree.size() - 1 ); tree[0].item = items.begin(); // reset root pointer tree[0].item.index = 0; }; //////////////////////////////////////////////////////////////////////////////// // put_normalized float& template void hat::put_normalized( T const & in, const float & in_weight ) { items.push_back( in ); iterator iter( --items.end(), items.size()-1); float normalized = in_weight; if(normalized <= 0) normalized = 1.0/NORMALIZE; weight_type chances = int( normalized * NORMALIZE ); sum_chances += chances; node new_node( iter, chances ); tree.push_back( new_node ); update_weights( tree.size() - 1 ); tree[0].item = items.begin(); // reset root pointer tree[0].item.index = 0; }; //////////////////////////////////////////////////////////////////////////////// // get() template T& hat::get() { weight_type target = random( tree[0].family_weight ); index_type location = find_index( 0, target ); return *tree[ location ].item; }; //////////////////////////////////////////////////////////////////////////////// // peek template typename hat::iterator hat::peek() { weight_type random_weight = random( tree[0].family_weight ); return tree[ find_index(0, random_weight) ].item; }; //////////////////////////////////////////////////////////////////////////////// // pull template T& hat::pull( T& storage ) { // get random item weight_type random_weight = random( tree[0].family_weight ); index_type index = find_index( 0, random_weight ); storage = *tree[index].item; erase( tree[index].item ); return storage; }; //////////////////////////////////////////////////////////////////////////////// // update_weights template void hat::update_weights( index_type index ) { // subscript validation if( index >= tree.size() ){ throw std::out_of_range("hat::update_weights() invalid index"); } // update the weight of the currently indexed node tree[index].family_weight = tree[index].weight; // daughters are found at 2(x+1) if( 2 * index + 2/*daughter(index)*/ < tree.size() ) { // add daughter tree[index].family_weight += tree[ 2 * index + 2/*daughter(index)*/ ].family_weight; } // else no daughter // sons are found at 2x+1 if( 2 * index + 1/*son(index)*/ < tree.size() ) { // add son tree[index].family_weight += tree[ 2 * index + 1/*son(index)*/ ].family_weight; } // else no son // check for parents, and update them if( index > 0 ) // not the root mother (has parent) { // calculate distance to parent if( index % 2 ) { // is a male node update_weights( (index-1)/2 ); // update parent } else { // is a female node update_weights( index/2 - 1 ); // update parent } } // else this is the root mother (no parents) }; //////////////////////////////////////////////////////////////////////////////// // find_index template index_type hat::find_index( index_type index, weight_type target ) { // subscript validation if( index >= tree.size() ){ throw std::out_of_range("hat::find_index() invalid index"); } // weight validation if( target > tree[index].family_weight ) { throw std::out_of_range("hat::find_index() weight outside tree"); } // divide weight into three groups: Self, Son & Daughter weight_type sum = tree[index].weight; if( target < sum ) { return index; } // else outside myself, must be in children // check son (first-born is always a son) sum += tree[ 2*index+1/*son(index)*/ ].family_weight; if( target < sum ) { return find_index( 2*index+1/*son(index)*/, target - tree[index].weight ); } // else not in son's range // check daughter sum += tree[ 2*index+2/*daughter(index)*/ ].family_weight; if( target < sum ) { return find_index( 2*index+2/*daughter(index)*/, target - tree[index].weight - tree[ 2 * index + 1/*son(index)*/ ].family_weight ); } // Possible if a custom random generator fails to deliver [0, range) throw std::out_of_range("hat::find_index() bad index/weight pair"); }; //////////////////////////////////////////////////////////////////////////////// // set_weight template void hat::set_weight( iterator iter, const weight_type& in_weight ) { weight_type weight = in_weight; if( weight < 1 ) weight = 1; sum_chances = sum_chances - tree[iter.index].weight + weight; tree[iter.index].weight = weight; update_weights( iter.index ); }; //////////////////////////////////////////////////////////////////////////////// // set_weight_normalized template void hat::set_weight_normalized( iterator iter, const float& in_weight ) { float normalized = in_weight; if( normalized <= 0 ){ normalized = 1.0 / NORMALIZE; } int chances = int( normalized * NORMALIZE ); sum_chances = sum_chances - tree[iter.index].weight + chances; tree[iter.index].weight = chances; update_weights( iter.index ); }; //////////////////////////////////////////////////////////////////////////////// // erase template void hat::erase( iterator iter ) { index_type index = iter.index; items.erase( iter.iter ); // copy last node over this node, then update the weights tree[index] = tree[ tree.size() - 1 ]; tree[index].item.index = index; update_weights(index); // delete last node, then update those weights sum_chances -= tree[ iter.index ].weight; tree.pop_back(); if( tree.empty() ) return; // calculate distance to previous parent if( tree.size() % 2 ) { // was a male node update_weights( (tree.size()-1)/2 ); // update parent } else { // was a female node update_weights( tree.size()/2 - 1 ); // update parent } }; //////////////////////////////////////////////////////////////////////////////// // copy constructor template hat::hat( const hat &in ) { tree = in.tree; // vector of nodes with iterators to be updated items = in.items; // list of value_type items sum_chances = in.sum_chances; // point iterators at new list of items index_type index = 0; for( typename std::list::iterator i = items.begin(); i != items.end(); ++i ) { tree[index++].item = i; } } //////////////////////////////////////////////////////////////////////////////// // assignment operator template hat& hat::operator=( const hat &in ) { /* todo: return on self-assignment */ tree = in.tree; // vector of nodes with iterators to be updated items = in.items; // list of value_type items sum_chances = in.sum_chances; // point iterators at new list of items index_type index = 0; for( typename std::list::iterator i = items.begin(); i != items.end(); ++i ) { tree[index++].item = i; } } #endif