C++ sorted vector: Difference between revisions
 (Created page with "The following class extends std::vector, adding sorting support.  Most importantly to me, it can do an actual search in a logical manner, unlike the out-of-the-box functionality ...")  | 
				No edit summary  | 
				||
| (9 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
This simple class extends std::vector, adding sorting support.  | |||
<pre><  | The class is the result of years of hammering on real-world problems.  std::vector<> is lean and mean, and this extension maintains that leanness.  | ||
In my experience, the objects I need to collect and sort typically have the key embedded in the object.  For that case, a sorted vector makes more sense than std::map or other alternatives.  | |||
The most useful part of this class has been the find_it_or_fail() method.  This minor extension to standard STL functionality lets you find a target object using a key object, similar to lower_bound(), but you are automatically notified if the search fails, via a return value of std::vector::end().  | |||
I haven't simplified this with C++11 lambdas etc. yet, as my cross-platform code uses compilers that aren't ready for that yet.  Hopefully soon.  | |||
Check the code comments for more.  Dump feedback [https://bitpost.com/news/2011/cross-platform-c-sorted_vector-derived-from-stdvector/ here].  Peace out!  | |||
<pre>  | |||
//-------------------------------------------------------------------//  | |||
//	Copyright © 2001-2011 A better Software.  | |||
//  | |||
// Use at your own risk.  :>  | |||
//  | |||
// History  | |||
// -------  | |||
// v0.1 1999? A long time ago in a compiler far far away...  | |||
// .  | |||
// v0.9 12/1/2010   Added predicate function support  | |||
// v1.0 1/23/2010   Class first archived and documented on wiki  | |||
                    https://www.thedigitalmachine.com/wiki/index.php/C%2B%2B_sorted_vector  | |||
// v1.1 1/24/2010   Fixed predicate version of find_it_or_fail to actually use predicate  | |||
// v1.2 2/04/2011   Added find_ptr_or_fail  | |||
//-------------------------------------------------------------------//  | |||
#if !defined(__SORTED_VECTOR_H)  | |||
#define __SORTED_VECTOR_H  | |||
#include <vector>  | |||
#include <algorithm>			// For lower_bound  | |||
| Line 18: | Line 49: | ||
//  | //  | ||
//		METHOD 2  | //		METHOD 2  | ||
//			Allow batch insertion without sorting with push_unsorted(); then    | //			Allow batch insertion without sorting with push_unsorted(); then  | ||
//			provide an additional call to sort the vector;  before  | //			provide an additional call to sort the vector;  before  | ||
//			searching for an item, the vector is always sorted if needed;  | //			searching for an item, the vector is always sorted if needed;  | ||
| Line 70: | Line 101: | ||
//      http://en.wikipedia.org/wiki/C%2B%2B0x#Polymorphic_wrappers_for_function_objects  | //      http://en.wikipedia.org/wiki/C%2B%2B0x#Polymorphic_wrappers_for_function_objects  | ||
//  | //  | ||
//   | // WARNING: if you change the key value of any object in the vector,  | ||
// the   | // you have unsorted the array without marking it as such.  Make sure  | ||
//  | // you call SetSorted(false) where appropriate.  | ||
//  | //  | ||
//-------------------------------------------------------------------//  | //-------------------------------------------------------------------//  | ||
| Line 79: | Line 109: | ||
class sorted_vector : public std::vector<T, _A>  | class sorted_vector : public std::vector<T, _A>  | ||
{  | {  | ||
    typedef std::vector<T> inherited;  | |||
public:  | 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()                                                            //  |      // sort()                                                            //  | ||
    //-------------------------------------------------------------------//  | |||
     // This function sorts the data as needed.  Call it after repeated calls to  |      // 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.  |      // push_unsorted(), or just let other members call it for you on next access.  | ||
     // It calls std::sort(), which defaults to using operator<() for  |      // It calls std::sort(), which defaults to using operator<() for  | ||
     // comparisons.  |      // comparisons.  | ||
    //-------------------------------------------------------------------//  | |||
    void sort()  | |||
    {  | |||
        if ( !m_bSorted )  | |||
        {  | |||
             std::sort( inherited::begin(), inherited::end() );  |              std::sort( inherited::begin(), inherited::end() );  | ||
            SetSorted();  | |||
        }  | |||
    }  | |||
     // This function is stupid.  binary_search() was a dumb design, STL peeps.  |      // 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 );  | |||
    }  | |||
     typename std::vector<T>::iterator lower_bound_it( const T& key )  | |||
     {  |      {  | ||
         if ( !m_bSorted )  |          if ( !m_bSorted )  | ||
| Line 128: | Line 158: | ||
         typename std::vector<T>::iterator it = std::lower_bound( inherited::begin(), inherited::end(), key );  |          typename std::vector<T>::iterator it = std::lower_bound( inherited::begin(), inherited::end(), key );  | ||
        return it;  | |||
    }  | |||
    /*const*/ T* lower_bound_ptr( const T& key )  | |||
    {  | |||
        typename std::vector<T>::iterator it = lower_bound_it( key );  | |||
         if (it==inherited::end())  |          if (it==inherited::end())  | ||
| Line 134: | Line 170: | ||
         /*const*/ T* t = &(*it);  |          /*const*/ T* t = &(*it);  | ||
         return t;  |          return t;  | ||
     }  |      }  | ||
| Line 154: | Line 182: | ||
     // This is the function you want to use most of the time  |      // This is the function you want to use most of the time  | ||
     // (or the predicate version if you are using object pointers).  |      // (or the predicate version if you are using object pointers).  | ||
     //  |      //  | ||
     // USAGE: it makes most sense to use this function if you have  |      // USAGE: it makes most sense to use this function if you have  | ||
| Line 165: | Line 189: | ||
     // function, and use the result to extract the additional information  |      // function, and use the result to extract the additional information  | ||
     // from the object.  |      // from the object.  | ||
     //-------------------------------------------------------------------//  |      //-------------------------------------------------------------------//  | ||
      typename std::vector<T>::iterator find_it_or_fail( const T& key )  |       typename std::vector<T>::iterator find_it_or_fail( const T& key )  | ||
      {  |       {  | ||
          typename std::vector<T>::iterator it = lower_bound_it( key );  |           typename std::vector<T>::iterator it = lower_bound_it( key );  | ||
          if ( it != inherited::end() )  | |||
              // lower_bound() does not necessarily indicate a successful search.  | |||
              // The iterator points to the object where an insertion  | |||
              // should take place.  We check that result to see if we actually  | |||
              // had an exact match.  | |||
              // 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();  | |||
      }  |       }  | ||
     //-------------------------------------------------------------------//  | |||
     // find_ptr_or_fail()                                                 //  | |||
     //-------------------------------------------------------------------//  | |||
     // A variation of find_it_or_fail() that provides a pointer to result.  | |||
     //-------------------------------------------------------------------//  | |||
      T* find_ptr_or_fail( const T& key )  | |||
      {  | |||
          typename std::vector<T>::iterator it = find_it_or_fail( key );  | |||
          if ( it != inherited::end() )  | |||
              return &(*it);  | |||
          return 0;  | |||
      }  | |||
     //-------------------------------------------------------------------//  |      //-------------------------------------------------------------------//  | ||
| Line 190: | Line 234: | ||
     // using push_unsorted() for each, then calling sort().  |      // 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);  |          push_back(t);  | ||
    }  | |||
     //-------------------------------------------------------------------//  |      //-------------------------------------------------------------------//  | ||
| Line 229: | Line 273: | ||
     // If you need to use a predicate sort function, ALWAYS use these methods  |      // If you need to use a predicate sort function, ALWAYS use these methods  | ||
     // instead of the non-functor versions.  |      // instead of the non-functor versions.  | ||
     // NOTE: UPDATE THIS   |      // NOTE: UPDATE THIS when C++0x polymorphic function wrappers are available.  | ||
    template<class _Pr> inline  |     template<class _Pr> inline  | ||
     void sort( _Pr pr )  |      void sort( _Pr pr )  | ||
| Line 238: | Line 282: | ||
             SetSorted();  |              SetSorted();  | ||
         }  |          }  | ||
     }  |      }  | ||
     template<class _Pr> inline  |      template<class _Pr> inline  | ||
| Line 267: | Line 294: | ||
          return it;  |           return it;  | ||
      }  |       }  | ||
    template<class _Pr> inline  | |||
    /*const*/ T* lower_bound_ptr( const T& key, _Pr pr )  | |||
    {  | |||
        typename std::vector<T>::iterator it = lower_bound_it( key, pr );  | |||
        if (it==inherited::end())  | |||
            return 0;  | |||
        /*const*/ T* t = &(*it);  | |||
        return t;  | |||
    }  | |||
      template<class _Pr> inline  |       template<class _Pr> inline  | ||
      void push_sorted( const T& t, _Pr pr )  |       void push_sorted( const T& t, _Pr pr )  | ||
| Line 283: | Line 321: | ||
       {  |        {  | ||
           typename std::vector<T>::iterator it = lower_bound_it( key, pr );  |            typename std::vector<T>::iterator it = lower_bound_it( key, pr );  | ||
           if ((*it)  | |||
           if ( it != inherited::end() )  | |||
              // NOTE: We have to apply this using the predicate function, be careful...  | |||
              if (!(pr((*it), key)) && !(pr(key,(*it))))  | |||
                  return it;  | |||
          return inherited::end();  | |||
      }  | |||
      template<class _Pr> inline  | |||
      T* find_ptr_or_fail( const T& key, _Pr pr )  | |||
      {  | |||
           typename std::vector<T>::iterator it = find_it_or_fail( key, pr );  | |||
          if ( it != inherited::end() )  | |||
              return &(*it);  | |||
          return 0;  | |||
       }  |        }  | ||
protected:  | protected:  | ||
    bool m_bSorted;  | |||
};  | };  | ||
#endif // __SORTED_VECTOR_H  | |||
</pre>  | |||
Latest revision as of 17:57, 26 August 2022
This simple class extends std::vector, adding sorting support.
The class is the result of years of hammering on real-world problems. std::vector<> is lean and mean, and this extension maintains that leanness.
In my experience, the objects I need to collect and sort typically have the key embedded in the object. For that case, a sorted vector makes more sense than std::map or other alternatives.
The most useful part of this class has been the find_it_or_fail() method. This minor extension to standard STL functionality lets you find a target object using a key object, similar to lower_bound(), but you are automatically notified if the search fails, via a return value of std::vector::end().
I haven't simplified this with C++11 lambdas etc. yet, as my cross-platform code uses compilers that aren't ready for that yet. Hopefully soon.
Check the code comments for more. Dump feedback here. Peace out!
//-------------------------------------------------------------------//
//	Copyright © 2001-2011 A better Software.
//
// Use at your own risk.  :>
//
// History
// -------
// v0.1 1999? A long time ago in a compiler far far away...
// .
// v0.9 12/1/2010   Added predicate function support
// v1.0 1/23/2010   Class first archived and documented on wiki
                    https://www.thedigitalmachine.com/wiki/index.php/C%2B%2B_sorted_vector
// v1.1 1/24/2010   Fixed predicate version of find_it_or_fail to actually use predicate
// v1.2 2/04/2011   Added find_ptr_or_fail
//-------------------------------------------------------------------//
#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
//
// 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.
//
//-------------------------------------------------------------------//
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 );
    }
    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;
    }
    /*const*/ T* lower_bound_ptr( const T& key )
    {
        typename std::vector<T>::iterator it = lower_bound_it( key );
        if (it==inherited::end())
            return 0;
        /*const*/ T* t = &(*it);
        return t;
    }
    //-------------------------------------------------------------------//
    // 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.
    //-------------------------------------------------------------------//
     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() )
              // lower_bound() does not necessarily indicate a successful search.
              // The iterator points to the object where an insertion
              // should take place.  We check that result to see if we actually
              // had an exact match.
              // 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();
     }
     //-------------------------------------------------------------------//
     // find_ptr_or_fail()                                                 //
     //-------------------------------------------------------------------//
     // A variation of find_it_or_fail() that provides a pointer to result.
     //-------------------------------------------------------------------//
      T* find_ptr_or_fail( const T& key )
      {
          typename std::vector<T>::iterator it = find_it_or_fail( key );
          if ( it != inherited::end() )
              return &(*it);
          return 0;
      }
    //-------------------------------------------------------------------//
    // 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 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
     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
    /*const*/ T* lower_bound_ptr( const T& key, _Pr pr )
    {
        typename std::vector<T>::iterator it = lower_bound_it( key, pr );
        if (it==inherited::end())
            return 0;
        /*const*/ T* t = &(*it);
        return t;
    }
     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: We have to apply this using the predicate function, be careful...
              if (!(pr((*it), key)) && !(pr(key,(*it))))
                  return it;
          return inherited::end();
      }
      template<class _Pr> inline
      T* find_ptr_or_fail( const T& key, _Pr pr )
      {
          typename std::vector<T>::iterator it = find_it_or_fail( key, pr );
          if ( it != inherited::end() )
              return &(*it);
          return 0;
      }
protected:
    bool m_bSorted;
};
#endif // __SORTED_VECTOR_H