Search code examples
c++iteratoruniquepredicatedeque

C++11 STL unique function, but in reverse


Is there a function which operates like std::unique, but that takes a custom comparison predicate, and retains the last element in an equivalent sequence, instead of the first? The answer is yes if C++14 or C++17 is an option; however I am using C++11.

I am starting with a deque of large heavy objects, sorted by one lightweight field. Some objects have equal values for the lightweight field, and this is unacceptable. I need to discard all but the last object in any sequence with a matching light field.

At present my code calls equal_range using a custom binary predicate and a helper heavy object, then rewinds the end iterator:

deque<Heavy> heavyDeque(...);

Light helperLight(...);
Heavy helperHeavy(helperLight, );

typedef deque<Heavy>:: iterator HevIt;

pair<HevIt, HevIt> deleteRange = equal_range(
    heavyDeque.begin(), heavyDeque.end(), helperHeavy,
    [](const Heavy & lhs, const Heavy & rhs) {
        return lhs.getLight() < rhs.getLight()})

//I have prior knowledge of at least one match
assert(deleteRange.first != deleteRange.second);
// back up; don't delete the last one
--(deleteRange.second);
heavyDeque.erase(deleteRange.first, deleteRange.second);

I am not too happy with having to pass in helperHeavy, when all I need from inside it is helperLight. I would rather my code look like this:

pair<HevIt, HevIt> deleteRange = magical_range_search(
    heavyDeque.begin(), heavyDeque.end(),
    [helperLight](const Heavy & heavy) {
        return helperLight == heavy.getLight()})

Note that my imaginary magical function takes a unary predicate, not a binary one.


Solution

  • something like this, perhaps?

    template<typename BidirectionalIterator, typename UnaryPredicate>
    pair<BidirectionalIterator,BidirectionalIterator>
    magical_range_search(BidirectionalIterator begin,
                         BidirectionalIterator end, UnaryPredicate p)
    {
      return {find_if(begin,end,p),
              find_if(make_reverse_iterator(end),make_reverse_iterator(begin),p)};
    }
    

    But, of course, for the whole thing you can just use std::unique with a reverse_iterator, as indicated in the comments:

    heavyContainer.erase(heavyContainer.begin(),
                         unique(make_reverse_iterator(heavyContainer.end()),
                                make_reverse_iterator(heavyContainer.begin()),
                                [](Heavy const&lhs, Heavy const&rhs) {
                                    return lhs.getLight() < rhs.getLight();
                                }).base());
    

    Here reverse_iterator<>::base() returns the underlying original iterator of the reverse_iterator returned by unique(). As unique() returns the new end of the sequence, the reverse operation returns the new begin of the sequence. The new range is {new_begin, orignal_end}, and the elements in the range {original_begin, new_begin} must be erase()d.