C++ sorted vector

From Bitpost wiki
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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