C++ sorted vector: Difference between revisions
No edit summary |
No edit summary |
||
Line 182: | Line 182: | ||
typename std::vector<T>::iterator it = lower_bound_it( key ); | typename std::vector<T>::iterator it = lower_bound_it( key ); | ||
if ( it != inherited::end() ) | |||
if( | |||
// NOTE: This is how the STL determines equality using only operator()<. | |||
// Two comparisons, ugg, but it is a nice little trick. | |||
if( !((*it)<key) && !(key<(*it)) ) | |||
return it; | |||
return inherited::end(); | |||
} | } | ||
Line 295: | Line 297: | ||
typename std::vector<T>::iterator it = lower_bound_it( key, pr ); | typename std::vector<T>::iterator it = lower_bound_it( key, pr ); | ||
// NOTE: This is how the STL determines equality using only operator()<. | if ( it != inherited::end() ) | ||
// NOTE: This is how the STL determines equality using only operator()<. | |||
// Two comparisons, ugg, but it is a nice little trick. | |||
if( !((*it)<key) && !(key<(*it)) ) | |||
return it; | |||
return inherited::end(); | |||
} | } | ||
Revision as of 21:25, 21 January 2011
The following class extends std::vector, adding sorting support. Most importantly to me is the find_it_or_fail() method. Check the code comments for more.
//-------------------------------------------------------------------// // Copyright © 2001-2011 A better Software. // // Use at your own risk. :> //-------------------------------------------------------------------// #if !defined(__SORTED_VECTOR_H) #define __SORTED_VECTOR_H #include <vector> #include <algorithm> // For lower_bound //-------------------------------------------------------------------// // sorted_vector // //-------------------------------------------------------------------// // This class is derived from std::vector, and adds sort tracking. // There are two basic ways to use a sorted vector: // // METHOD 1 // Always maintain sort order by inserting with push_sorted() - // the location of new items is determined before inserting; // since the vector remains sorted, this doesn't take too long // (although for large batch insertions METHOD 2 is definitely // faster); // // METHOD 2 // Allow batch insertion without sorting with push_unsorted(); then // provide an additional call to sort the vector; before // searching for an item, the vector is always sorted if needed; // // Of course you need to provide an operator()< for the type of object // you're sorting, if it doesn't have one. Example: /* class MyClass { public: bool operator< (const MyClass left) const { if ( left.m_nMostImportant == m_nMostImportant ) return left.m_nLeastImportant < m_nLeastImportant; return left.m_nMostImportant < m_nMostImportant; } int m_nMostImportant; int m_nLeastImportant; } */ // // NOTE: C++ doesn't let you use an operator()< for POINTERS. This // breaks down when creating the template code, as you end up with // a ref to a ref which is not allowed (or something :P). // So if you have a vector of pointers, here's what you have to do: // // FOR NOW, with straight C++, create a less-than functor, then // pass that in to the functor versions of the class methods below. // Create a functor, aka function object, as follows: // // struct my_class_lessthan // { // bool operator()(const MyClass* left, const MyClass* right) // { // return left->get_timestamp() < right->get_timestamp(); // } // }; // // Usage example: // // sorted_vector<MyClass*> svpMC; // svpMC.push_unsorted(new MyClass(blah, blah); // svpMC.push_unsorted(new MyClass(blah, blah); // vpMC.sort( my_class_lessthan() ); // // Once C++0x is available, I need to update this class to use // a function object wrapper, and allow the user to set it // in the constructor, then always use it automatically. // http://en.wikipedia.org/wiki/C%2B%2B0x#Polymorphic_wrappers_for_function_objects // // FUN NOTE: you can cause an internal compiler error if you leave off // the part bracketed by #... // // class sorted_vector : public std::vector#<T># // //-------------------------------------------------------------------// template<class T, class _A = std::allocator<T> > class sorted_vector : public std::vector<T, _A> { typedef std::vector<T> inherited; public: //-------------------------------------------------------------------// // SetSorted() // //-------------------------------------------------------------------// // I didn't want to override every constructor to set this // member variable, so this function is publicly accessible. // You should call SetSorted( true ) right after construction. // TO DO: if you feel like it... derive all constructors to avoid // the need for this. There are 4 last time I checked. //-------------------------------------------------------------------// void SetSorted( bool bSorted = true ) { m_bSorted = bSorted; } //-------------------------------------------------------------------// // sort() // //-------------------------------------------------------------------// // This function sorts the data as needed. Call it after repeated calls to // push_unsorted(), or just let other members call it for you on next access. // It calls std::sort(), which defaults to using operator<() for // comparisons. //-------------------------------------------------------------------// void sort() { if ( !m_bSorted ) { std::sort( inherited::begin(), inherited::end() ); SetSorted(); } } // This function is stupid. binary_search() was a dumb design, STL peeps. bool bContains( const T& t ) { if ( !m_bSorted ) { sort(); } return std::binary_search( inherited::begin(), inherited::end(), t ); } /*const*/ T* lower_bound( const T& key ) { if ( !m_bSorted ) sort(); typename std::vector<T>::iterator it = std::lower_bound( inherited::begin(), inherited::end(), key ); if (it==inherited::end()) return 0; /*const*/ T* t = &(*it); return t; } typename std::vector<T>::iterator lower_bound_it( const T& key ) { if ( !m_bSorted ) sort(); typename std::vector<T>::iterator it = std::lower_bound( inherited::begin(), inherited::end(), key ); return it; } //-------------------------------------------------------------------// // find_it_or_fail() // //-------------------------------------------------------------------// // This function takes the given object and determines if there is // a match in the vector. It returns an iterator to the actual // object in the vector, if found. Otherwise returns std::vector::end(). // // This is the function you want to use most of the time // (or the predicate version if you are using object pointers). // // USAGE: it makes most sense to use this function if you have // an object with a key, other member variables, and operator<() // that uses the key to test for equality. You then set up a dummy // "search" object with the key set to the search value, call the // function, and use the result to extract the additional information // from the object. // // WARNING: if you change the key value of any object in the vector, // you have unsorted the array without marking it as such. Make sure // you call SetSorted(false) where appropriate. // //-------------------------------------------------------------------// typename std::vector<T>::iterator find_it_or_fail( const T& key ) { typename std::vector<T>::iterator it = lower_bound_it( key ); if ( it != inherited::end() ) // NOTE: This is how the STL determines equality using only operator()<. // Two comparisons, ugg, but it is a nice little trick. if( !((*it)<key) && !(key<(*it)) ) return it; return inherited::end(); } //-------------------------------------------------------------------// // push_sorted() // //-------------------------------------------------------------------// // This is used to insert into a vector that always remains sorted. // Because we have a sorted vector, finding the insertion location // with std::lower_bound() is relatively cheap. // // If you have multiple insertions, consider // using push_unsorted() for each, then calling sort(). //-------------------------------------------------------------------// void push_sorted( const T& t ) { if ( !m_bSorted ) { sort(); } // Insert at "lower_bound" (the proper sorted location). insert( std::lower_bound( inherited::begin(), inherited::end(), t ), t ); } //-------------------------------------------------------------------// // push_unsorted() // //-------------------------------------------------------------------// // This is similar to push_back(), but in addition, it sets the // unsorted flag. //-------------------------------------------------------------------// void push_unsorted( const T& t ) { SetSorted( false ); push_back(t); } //-------------------------------------------------------------------// // operator=() // //-------------------------------------------------------------------// // This allows us to set the sorted_vector from a std::vector. //-------------------------------------------------------------------// sorted_vector<T>& operator=(std::vector<T>& v) { typename std::vector<T>::iterator it; for (it= v.begin(); it != v.end(); ++it) push_unsorted((*it)); return this; } // CALLS WHERE YOU PROVIDE THE FUNCTOR OR FUNCTION POINTER // If you need to use a predicate sort function, ALWAYS use these methods // instead of the non-functor versions. // NOTE: UPDATE THIS SHITTINESS when C++0x polymorphic function wrappers are available. template<class _Pr> inline void sort( _Pr pr ) { if ( !m_bSorted ) { std::sort( inherited::begin(), inherited::end(), pr ); SetSorted(); } } template<class _Pr> inline /*const*/ T* lower_bound( const T& key, _Pr pr ) { if ( !m_bSorted ) { std::sort( inherited::begin(), inherited::end(), pr ); SetSorted(); } typename std::vector<T>::iterator it = std::lower_bound( inherited::begin(), inherited::end(), key, pr ); if (it==inherited::end()) return 0; /*const*/ T* t = &(*it); return t; } template<class _Pr> inline typename std::vector<T>::iterator lower_bound_it( const T& key, _Pr pr ) { if ( !m_bSorted ) { std::sort( inherited::begin(), inherited::end(), pr ); SetSorted(); } typename std::vector<T>::iterator it = std::lower_bound( inherited::begin(), inherited::end(), key, pr ); return it; } template<class _Pr> inline void push_sorted( const T& t, _Pr pr ) { if ( !m_bSorted ) { std::sort( inherited::begin(), inherited::end(), pr ); SetSorted(); } // Insert at "lower_bound" (the proper sorted location). insert( std::lower_bound( inherited::begin(), inherited::end(), t, pr ), t ); } template<class _Pr> inline typename std::vector<T>::iterator find_it_or_fail( const T& key, _Pr pr ) { typename std::vector<T>::iterator it = lower_bound_it( key, pr ); if ( it != inherited::end() ) // NOTE: This is how the STL determines equality using only operator()<. // Two comparisons, ugg, but it is a nice little trick. if( !((*it)<key) && !(key<(*it)) ) return it; return inherited::end(); } protected: bool m_bSorted; }; #endif // __SORTED_VECTOR_H