Comparison tricks in C++

前端 未结 10 1849
礼貌的吻别
礼貌的吻别 2021-01-31 16:13

A class:

class foo{
public:
    int data;
};

Now I want to add a method to this class, to do some comparison, to see if its data is equal to on

相关标签:
10条回答
  • 2021-01-31 16:24

    set is a good option, but if you really want to roll your own, initializer_list is convienient:

    bool is_in( int val, initializer_list<int> lst )
    {
        for( auto i : lst )
            if( i == val ) return true;
        return false;
    }
    

    use is trivial:

    is_in( x, { 3, 5, 7 } ) ;
    

    it's O(n) thou, set / unordered is faster

    0 讨论(0)
  • 2021-01-31 16:33

    The easy way

    The simplest approach is to write a member function wrapper called in() around std::find with a pair of iterators to look for the data in question. I wrote a simple template<class It> in(It first, It last) member function for that

    template<class It>
    bool in(It first, It last) const
    {
        return std::find(first, last, data) != last;
    }
    

    If you have no access to the source of foo, you can write a non-member functions of signature template<class T> bool in(foo const&, std::initializer_list<T>) etc., and call it like

    in(f, {1, 2, 3 });
    

    The hard way

    But let's go completely overboard with that: just add two more public overloads:

    • one taking a std::initializer_list parameter that calls the previous one with the begin() and end() iterators of the corresponding initializer list argument.
    • one for an arbitrary container as input that will do a little tag dispatching to two more private overloads of a detail_in() helper:
      • one overload doing a SFINAE trick with trailing return type decltype(c.find(data), bool()) that will be removed from the overload set if the container c in question does not have a member function find(), and that returns bool otherwise (this is achieved by abusing the comma operator inside decltype)
      • one fallback overload that simply takes the begin() and end() iterators and delegates to the original in() taking two iterators

    Because the tags for the detail_in() helper form an inheritance hierarchy (much like the standard iterator tags), the first overload will match for the associative containers std::set and std::unordered_set and their multi-cousins. All other containers, including C-arrays, std::array, std::vector and std::list, will match the second overload.

    #include <algorithm>
    #include <array>
    #include <initializer_list>
    #include <type_traits>
    #include <iostream>
    #include <set>
    #include <unordered_set>
    #include <vector>
    
    class foo
    {
    public:
        int data;
    
        template<class It>
        bool in(It first, It last) const
        {
            std::cout << "iterator overload: ";
            return std::find(first, last, data) != last;
        }
    
        template<class T>
        bool in(std::initializer_list<T> il) const
        {
            std::cout << "initializer_list overload: ";
            return in(begin(il), end(il));
        }
    
        template<class Container>
        bool in(Container const& c) const 
        {
            std::cout << "container overload: ";
            return detail_in(c, associative_container_tag{});    
        }
    
    private:
        struct sequence_container_tag {};
        struct associative_container_tag: sequence_container_tag {};
    
        template<class AssociativeContainer>
        auto detail_in(AssociativeContainer const& c, associative_container_tag) const 
        -> decltype(c.find(data), bool())
        {
            std::cout << "associative overload: ";
            return c.find(data) != end(c);    
        }
    
        template<class SequenceContainer> 
        bool detail_in(SequenceContainer const& c, sequence_container_tag) const
        {
            std::cout << "sequence overload: ";
            using std::begin; using std::end;
            return in(begin(c), end(c));    
        }
    };
    
    int main()
    {
        foo f{1};
        int a1[] = { 1, 2, 3};
        int a2[] = { 2, 3, 4};
    
        std::cout << f.in({1, 2, 3}) << "\n";
        std::cout << f.in({2, 3, 4}) << "\n";
    
        std::cout << f.in(std::begin(a1), std::end(a1)) << "\n";
        std::cout << f.in(std::begin(a2), std::end(a2)) << "\n";
    
        std::cout << f.in(a1) << "\n";
        std::cout << f.in(a2) << "\n";
    
        std::cout << f.in(std::array<int, 3>{ 1, 2, 3 }) << "\n";
        std::cout << f.in(std::array<int, 3>{ 2, 3, 4 }) << "\n";
    
        std::cout << f.in(std::vector<int>{ 1, 2, 3 }) << "\n";
        std::cout << f.in(std::vector<int>{ 2, 3, 4 }) << "\n";
    
        std::cout << f.in(std::set<int>{ 1, 2, 3 }) << "\n";
        std::cout << f.in(std::set<int>{ 2, 3, 4 }) << "\n";
    
        std::cout << f.in(std::unordered_set<int>{ 1, 2, 3 }) << "\n";
        std::cout << f.in(std::unordered_set<int>{ 2, 3, 4 }) << "\n";    
    }
    

    Live Example that -for all possible containers- prints 1 and 0 for both number sets.

    The use cases for the std::initializer_list overload are for member-ship testing for small sets of numbers that you write out explicitly in calling code. It has O(N) complexity but avoids any heap allocations.

    For anything heavy-duty like membership testing of large sets, you could store the numbers in an associative container like std::set, or its multi_set or unordered_set cousins. This will go to the heap when storing these numbers, but has O(log N) or even O(1) lookup complexity.

    But if you happen to have just a sequence container full of numbers around, you can also throw that to the class and it will happily compute membership for you in O(N) time.

    0 讨论(0)
  • 2021-01-31 16:35

    There are many ways of doing this with the STL.

    If you have an incredibly large number of items and you want to test if your given item is a member of this set, use set or unordered_set. They allow you to check membership in log n and constant time respectively.

    If you keep the elements in a sorted array, then binary_search will also test membership in log n time.

    For small arrays, a linear search might however preform significantly faster (as there is no branching). A linear search might even do 3-8 comparisons in the time it takes the binary search to 'jump around'. This blog post suggests there to be a break-even point at proximately 64 items, below which a linear search might be faster, though this obviously depends on the STL implementation, compiler optimizations and your architecture's branch prediction.

    0 讨论(0)
  • 2021-01-31 16:35

    I would recommend to use standard container like std::vector, but that would still imply a linear complexity with worst-case runtime of O(N).

    class Foo{
    public:
        int data;
        bool is_equal_to_one_of_these(const std::vector<int>& arguments){
            bool matched = false;
            for(int arg : arguments){ //if you are not using C++11: for(int i = 0; i < arguments.size(); i++){
                if( arg == data ){ //if you are not using C++11: if(arguments[i] == data){
                    matched = true;
                }
            }
            return matched;
        }
    };
    
    std::vector<int> exampleRange{ {1,2,3,4,5} };
    Foo f;
    f.data = 3;
    std::cout << f.is_equal_to_one_of_these(exampleRange); // prints "true"
    
    0 讨论(0)
  • 2021-01-31 16:37

    It isn't pretty, but this should work:

    class foo {
        bool equals(int a) { return a == data; }
        bool equals(int a, int b) { return (a == data) || (b == data); }
        bool equals(int a, int b, int c) {...}     
        bool equals(int a, int b, int c, int d) {...} 
    private:
        int data; 
    }
    

    And so on. That'll give you the exact syntax you were after. But if you are after the completely variable number of arguments then either the vector, or std::initalizer list might be the way to go:

    See: http://en.cppreference.com/w/cpp/utility/initializer_list

    This example shows it in action:

    #include <assert.h>
    #include <initializer_list>
    
    class foo {
    public:
            foo(int d) : data(d) {}
            bool equals_one_of(std::initializer_list<int> options) {
                    for (auto o: options) {
                            if (o == data) return true;
                    }
                    return false;
            }
    private:
            int data;
    };
    
    int main() {
            foo f(10);
            assert(f.equals_one_of({1,3,5,7,8,10,14}));
            assert(!f.equals_one_of({3,6,14}));
            return 0;
    }
    
    0 讨论(0)
  • 2021-01-31 16:39

    Does anyone have better idea ? thanks for sharing !

    There's a standard algitrithm for that:

    using std::vector; // & std::begin && std::end
    
    // if(data is equal to one of these(1,2,3,4,5,6))
    /* maybe static const */vector<int> criteria{ 1, 2, 3, 4, 5, 6 };
    return end(criteria) != std::find(begin(criteria), end(criteria), data);
    

    Edit: (all in one place):

    bool is_equal_to_one_of_these(int data, const std::vector<int>& criteria)
    {
        using std::end; using std::begin; using std::find;
        return end(criteria) != find(begin(criteria), end(criteria), data);
    }
    
    auto data_matches = is_equal_to_one_of_these(data, {1, 2, 3, 4, 5, 6});
    

    Edit:

    I prefer the interface in terms of a vector, instead of an initializer list, because it is more powerful:

    std:vector<int> v = make_candidate_values_elsewhere();
    auto data_matches = is_equal_to_one_of_these(data, v);
    

    The interface (by using a vector), doesn't restrict you to define the values, where you call is_equal_to_one_of_these.

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