Search code examples
c++c++11stlquicksortstl-algorithm

std::equal_range complains about "sequence not ordered"


I need help sorting a vector of elements and subsequently processing ranges on the sorted vector. The vector of strings is initially sorted using the default operator< predicate

Consider the following vector of unsorted strings

std::vector<std::string> strings = {
   "1A", "3C1", "1B", "1C1", "4D", "1C1"
}

std::sort(strings.begin(), strings.end(), std::less  <std::string > );

after sorting this ends up lexicogarphially sorting the strings to the following:

"1A", "1B", "1C1", "1C1", "3C1", "4D"

In my case I then want to iterate through these ordered strings breaking them into ranges [begin, end) using the first numeric character as the value comparator - I am not sure how to do this without getting visual studio indicating "sequence not ordered"

"1A", "1B", "1C1", "1C1"

the second group should contain

"3C1"

and the final group

"4D"

I have been experimenting with std::equal_range using a predicate that only searches for the leading digit, but it keeps indicating "sequence not ordered" (EDIT - this was a bug I had as I was using a comparator to equal_range that did not respect the existing order of the underlying collection). I think this is because the existing sorted range (which uses std::less as a predicate is different to my predicate to extract the sub ranges is the root of the problem. Any help on how to break this ordered list into the sub ranges would be greatly appreciated.

EDIT NOTES: Although the above question is answered, I would like to make the question clearer with a solid example so that others can learn where I faltered. I oversimplified the problem I was trying to solve, The actual problem uses a class with 4 fields (1 optional) and consequently, I did not provide sufficient detail to show where I was having problems with the equal_range parameters (specifically the value I was passing into the equal range and the corresponding comparator which was used to strict weak order the elements according to this comparator - which also had to respect the existing order of the underlying collection). As it turns out, the problem was surprisingly simple to solve - I simply had to change the explicit default PriorityValue constructor - taking these 4 args to a non explicit constructor where I could pass the "channel" field as a single arg constructor parameter. As a result my call to equal_range is as follows:

// lambda to compare channels in ascending order 
const auto channelComp = [](const PriorityLevel& a, const PriorityLevel& b) {
    return a.getChannel() < b.getChannel();
};    

auto range = std::equal_range(gPriorities.begin(), gPriorities.end(), 1, channelComp);
// print all common channel entries
std::cout << "priorites with channel [1]" << std::endl;
std::cout << "Channel, PriorityChar, filename, [optional sequence]" << std::endl;
std::copy(range.first, range.second,
    std::ostream_iterator<PriorityLevel>(std::cout, "\n"));

I have a live example in coliru that demonstrates where I fixed the problem - I hope this is useful.

unsorted
Channel, PriorityChar, filename, [optional sequence]
1A[foo._dr]
3A[bar._dr]
1B[foo._dr]1
1B[bar._dr]1
1B[foo._dr]2
1B[bar._dr]2
1B[foo._dr]3
1B[foo._dr]3
1B[foo._dr]4
1B[foo._dr]5
2A[foo._dr]
2B[foo._dr]1
2B[foo._dr]2
2B[foo._dr]3
2B[foo._dr]2

sorted - ascending
Channel, PriorityChar, filename, [optional sequence]
1A[foo._dr]
1B[bar._dr]1
1B[bar._dr]2
1B[foo._dr]1
1B[foo._dr]2
1B[foo._dr]3
1B[foo._dr]3
1B[foo._dr]4
1B[foo._dr]5
2A[foo._dr]
2B[foo._dr]1
2B[foo._dr]2
2B[foo._dr]2
2B[foo._dr]3
3A[bar._dr]

priorites with channel [1]
Channel, PriorityChar, filename, [optional sequence]
1A[foo._dr]
1B[bar._dr]1
1B[bar._dr]2
1B[foo._dr]1
1B[foo._dr]2
1B[foo._dr]3
1B[foo._dr]3
1B[foo._dr]4
1B[foo._dr]5

priorites with channel [2]
Channel, PriorityChar, filename, [optional sequence]
2A[foo._dr]
2B[foo._dr]1
2B[foo._dr]2
2B[foo._dr]2
2B[foo._dr]3

Solution

  • Here's a working example:

    #include <vector>
    #include <string>
    #include <algorithm>
    #include <functional>
    #include <iostream>
    #include <ostream>
    
    bool initial_char_comp( std::string const& s1, std::string const& s2)
    {
        if (s1.size() == 0 || s2.size() == 0) {
            return s1.size() < s2.size();
        }
    
        return s1[0] < s2[0];
    }
    
    
    int main(int argc, char ** argv) {
        std::vector<std::string> strings = {
           "1A", "3C1", "1B", "1C1", "4D", "1C1"
        };
    
        std::sort(strings.begin(), strings.end(), std::less<std::string>());
    
        for (auto i = strings.cbegin(); i != strings.cend(); ++i) {
            std::cout << *i << " ";
        }
    
        std::cout << std::endl;
    
        for (char c = '1'; c < '5'; ++c) {
            auto range = std::equal_range( strings.cbegin(), strings.cend(), std::string(1, c), initial_char_comp);
    
            std::cout << "range starting with '" << c << "': "; 
            for (auto i = range.first; i != range.second; ++i) {
                std::cout << *i << " ";
            }
            std::cout << std::endl;
        }
    
        return 0;
    }