C++ lambdas for std::sort and std::lower_bound/equal_range on a struct element in a sorted vector of structs

前端 未结 5 779
旧时难觅i
旧时难觅i 2020-12-16 18:10

I have a std::vector of this struct:

struct MS
{        
  double aT;
  double bT;
  double cT;
};

which I want to use std::sort on aswell

相关标签:
5条回答
  • 2020-12-16 18:40

    It's a little awkward, but if you check the definitions of lower_bound and upper_bound from the standard, you'll see that the definition of lower_bound puts the dereferenced iterator as the first parameter of the comparison (and the value second), whereas upper_bound puts the dereferenced iterator second (and the value first).

    So, I haven't tested this but I think you'd want:

    std::lower_bound(vec.begin(), vec.end(), 3.142, [](const MS &lhs, double rhs) {
        return lhs.aT < rhs;
    });
    

    and

    std::upper_bound(vec.begin(), vec.end(), 3.142, [](double lhs, const MS &rhs) {
        return lhs < rhs.aT;
    });
    

    This is pretty nasty, and without looking up a few more things I'm not sure you're actually entitled to assume that the implementation uses the comparator only in the way it's described in the text - that's a definition of the result, not the means to get there. It also doesn't help with binary_search or equal_range.

    It's not explicitly stated in 25.3.3.1 that the iterator's value type must be convertible to T, but it's sort of implied by the fact that the requirement for the algorithm is that T (in this case, double) must be LessThanComparable, not that T must be comparable to the value type of the iterator in any particular order.

    So I think it's better just to always use a lambda (or functor) that compares two MS structs, and instead of passing a double as a value, pass a dummy MS with the correct field set to the value you're looking for:

    std::upper_bound(vec.begin(), vec.end(), MS(3.142,0,0), [](const MS &lhs, const MS &rhs) {
        return lhs.aT < rhs.aT;
    });
    

    If you don't want to give MS a constructor (because you want it to be POD), then you can write a function to create your MS object:

    MS findA(double d) {
        MS result = {d, 0, 0};
        return result;
    }
    MS findB(double d) {
        MS result = {0, d, 0};
        return result;
    }
    

    Really, now that there are lambdas, for this job we want a version of binary search that takes a unary "comparator":

    double d = something();
    unary_upper_bound(vec.begin(), vec.end(), [d](const MS &rhs) {
        return d < rhs.aT;
    });
    

    C++0x doesn't provide it, though.

    0 讨论(0)
  • 2020-12-16 18:42

    In the definition of lower_bound and other STL Algorithms the Compare function is such that the first type must match that of the Forward Iterator and the second type must match that of T (i.e., of the value).

    template< class ForwardIt, class T, class Compare >
    ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );
    

    So one indeed can compare things from different objects (doing what the other response called an Unary Comparator). In C++11 :

    vector<MS> v = SomeSortedVectorofMSByFieldaT();
    double a_key;
    auto it = std::lower_bound(v.begin(), 
                               v.end(), 
                               a_key, 
                               []{const MS& m, const double& a) {
                                 m.aT < a; 
                               });
    

    And this can be used with other STL algorithm functions as well.

    0 讨论(0)
  • 2020-12-16 18:44

    I had the same problem for std::equal_range and came up with an alternative solution.

    I have a collection of pointers to objects sorted on a type field. I need to find the find the range of objects for a given type.

    const auto range = std::equal_range (next, blocks.end(), nullptr,
        [type] (Object* o1, Object* o2)
    {
        return (o1 ? o1->Type() : type) < (o2 ? o2->Type() : type);
    });
    

    Although it is less efficient than a dedicated predicate as it introduces an unnecessary nullptr test for each object in my collection, it does provide an interesting alternative.

    As an aside, when I do use a class as in your example, I tend to do the following. As well as being shorter, this allows me to add additional types with only 1 function per type rather then 4 operators per type.

    class MSbTLess 
    {
    private:
        static inline const double& value (const MS& val)
        {
            return val.bT;
        }
    
        static inline const double& value (const double& val)
        {
            return val;
        }
    
    public:
        template <typename T1, typename T2>
        bool operator() (const T1& lhs, const T2& rhs) const
        {
            return value (t1) < value (t2);
        }
    };
    
    0 讨论(0)
  • 2020-12-16 18:53

    The algorithms std::sort, std::lower_bound, and std::binary_search take a predicate that compares two elements of the container. Any lambda that compares two MS objects and returns true when they are in order should work for all three algorithms.

    0 讨论(0)
  • 2020-12-16 18:53

    Not directly relevant to what you're saying about lambdas, but this might be an idea for using the binary search functions:

    #include <iostream>
    #include <algorithm>
    #include <vector>
    
    struct MS
    {
        double aT;
        double bT;
        double cT;
        MS(double a, double b, double c) : aT(a), bT(b), cT(c) {}
    };
    
    // template parameter is a data member of MS, of type double
    template <double MS::*F>
    struct Find {
        double d;
        Find(double d) : d(d) {}
    };
    
    template <double MS::*F>
    bool operator<(const Find<F> &lhs, const Find<F> &rhs) {
        return lhs.d < rhs.d;
    }
    template <double MS::*F>
    bool operator<(const Find<F> &lhs, const MS &rhs) {
        return lhs.d < rhs.*F;
    }
    template <double MS::*F>
    bool operator<(const MS &lhs, const Find<F> &rhs) {
        return lhs.*F < rhs.d;
    }
    
    int main() {
        std::cout << (Find<&MS::bT>(1) < Find<&MS::bT>(2)) << "\n";
        std::cout << (Find<&MS::bT>(1) < MS(1,0,0)) << "\n";
        std::cout << (MS(1,0,0) < Find<&MS::bT>(1)) << "\n";
    
        std::vector<MS> vec;
        vec.push_back(MS(1,0,0));
        vec.push_back(MS(0,1,0));
        std::lower_bound(vec.begin(), vec.end(), Find<&MS::bT>(0.5));
        std::upper_bound(vec.begin(), vec.end(), Find<&MS::bT>(0.5));
    }
    

    Basically, by using Find as the value, we don't have to supply a comparator, because Find compares to MS using the field that we specify. This is the same kind of thing as the answer you saw over here: how to sort STL vector, but using the value rather than the comparator as in that case. Not sure if it'd be all that great to use, but it might be, since it specifies the value to search for and the field to search in a single short expression.

    0 讨论(0)
提交回复
热议问题