lower_bound == upper_bound

后端 未结 7 1029
陌清茗
陌清茗 2020-12-02 08:51

What does lower_bound mean. If I had to guess I would answer that this function returns the iterator at the last element that is less than the value asked for. But I see tha

7条回答
  •  甜味超标
    2020-12-02 09:21

    Following David Hammen's answer, I attempted to explain why we often don't feel the names of lower_bound/upper_bound to be correct, or at least intuitive.

    It's because we are looking for an element immediately lower than the query. I made a drawing and a use case:

    Code:

    template< typename T, typename U >
    auto infimum(std::map const& ctr, T query)
    {
        auto it = ctr.upper_bound(query);
        return it == ctr.begin() ? ctr.cend() : --it;
    }
    
    template< typename T, typename U >
    bool is_in_interval(std::map const& ctr, T query)
    {
        auto inf = infimum(ctr, query);
        return inf == ctr.end() ? false : query <= inf->second;
    }
    

    https://ideone.com/jM8pt3

    Basically to get the behavior of the "grey arrows", we need upper_bound - 1 which is why it's weird.

    Let me rephrase that slightly: from the name lower_bound we instinctively expect returns-first-immediately-inferior-element (like the grey arrows). But we get returns-first-immediately-superior-element for lower_bound; and first-immediately-strictly-superior-element for upper_bound. That's what is surprising.

    It's surprising in the hypothesis that you work with a sparse sequence like my thought experiment in the picture above. But it makes wonderful sense when you think of it in terms of «bounds of an equal_range» in a dense sequence, populated with plateaus, like Kerrek SB beautifully pictured.

    Test code:

    #include 
    #include 
    #include 
    
    // .. paste infimum and is_in_interval here
    
    int main()
    {
        using std::cout;
        using Map = std::map;
        Map intervals{{2,5}, {8,9}};
    
        auto red = infimum(intervals, 4);
        assert(red->first == 2);
        cout << "red->first " << red->first << "\n";
    
        auto green = infimum(intervals, 6);
        assert(green->first == 2);
        cout << "green->first " << green->first << "\n";
    
        auto pink = infimum(intervals, 8);
        assert(pink->first == 8);
        cout << "pink->first " << pink->first << "\n";
    
        auto yellow = infimum(intervals, 1);
        assert(yellow == intervals.cend());
    
        auto larger_than_all = infimum(intervals, 15);
        assert(larger_than_all->first == 8);
    
        bool red_is = is_in_interval(intervals, 4);
        cout << "red is in " << red_is << "\n";
    
        bool green_is = is_in_interval(intervals, 6);
        cout << "green is in " << green_is << "\n";
    
        bool pink_is = is_in_interval(intervals, 8);
        cout << "pink is in " << pink_is << "\n";
    
        bool yellow_is = is_in_interval(intervals, 1);
        cout << "yellow is in " << yellow_is << "\n";
    }
    

    results in

    red->first 2
    green->first 2
    pink->first 8
    red is in 1
    green is in 0
    pink is in 1
    yellow is in 0
    

    seems ok.

    So of course the utility functions are not very good, they should be designed with a range API, so we can work with any collection or sub-range, or reverse iterators, or filtered views and whatnot. We can get that when we have C++20. In the meantime, I just made a simple educative map-taking API.

    play with it:
    https://ideone.com/jM8pt3

提交回复
热议问题