C++中的比较技巧

Comparison tricks in C++

本文关键字:比较 C++      更新时间:2023-10-16

A class:

class foo{
public:
    int data;
};

现在我想向这个类添加一个方法,做一些比较,看看它的数据是否等于给定数字之一。

当然,我可以写if(data==num1|| data == num2|| data ==num3.....),但老实说,每次将其与数字进行比较时,我都会在写data ==时感到恶心。

所以,我希望我能写出这样的东西:

if(data is equal to one of these(num1,num2,num3,num4,num5...))
    return true;
else
    return false;

我想实现这个声明,data is equal to one of these(num1, num2, num3, num4, num5...)

这是我的方法:

#include <stdarg.h>
bool is_equal_to_one_of_these(int count,...){
    int i;
    bool equal = false;
    va_list arg_ptr;
    va_start(arg_prt,count);
    for(int x=0;x<count;x++){
        i = va_arg(arg_ptr,int);
        if( i == data ){
            equal = true;
            break;
        }
    }
    va_end(arg_ptr);
    return equal;
}

这段代码将为我完成这项工作。但是每次使用此方法时,我都必须计算参数并将其传入。

有人有更好的主意吗?

简单的方法

最简单的方法是使用一对迭代器围绕std::find编写一个名为 in() 的成员函数包装器来查找相关数据。我为此编写了一个简单的template<class It> in(It first, It last)成员函数

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

如果你无法访问 foo 的源代码,你可以编写一个签名template<class T> bool in(foo const&, std::initializer_list<T>)等的非成员函数,并像

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

艰难的方式

但是,让我们完全过火:只需再添加两个public重载:

  • 一个采用std::initializer_list参数,该参数使用 begin() 调用前一个参数,并end()相应初始值设定项列表参数的迭代器。
  • 一个用于任意容器作为输入,它将对detail_in()帮助程序的另外两个private重载执行一点标记调度:
    • 一个使用尾随返回类型执行 SFINAE 技巧的重载decltype(c.find(data), bool())如果所讨论的容器c没有成员函数find(),则会从重载集中删除,否则返回bool(这是通过滥用 decltype 中的逗号运算符来实现的(
    • 一个回退重载,只需获取begin()并将迭代器和委托end()到原始in()采用两个迭代器

由于detail_in()帮助程序的标记形成继承层次结构(与标准迭代器标记非常相似(,因此第一个重载将与关联容器std::setstd::unordered_set及其多表亲匹配。所有其他容器,包括 C 数组、std::arraystd::vectorstd::list,都将匹配第二个重载。

#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";    
}

实时示例对于所有可能的容器,为两个数字集打印 1 和 0。

std::initializer_list重载的用例是针对您在调用代码中显式写出小组数字集进行成员船测试。它具有O(N)复杂性,但避免了任何堆分配。

对于任何繁重的事情,如大型集合的成员资格测试,您可以将数字存储在关联容器中,例如std::set或其multi_setunordered_set表亲。在存储这些数字时,这将进入堆,但具有O(log N)甚至O(1)查找复杂性。

但是,如果您碰巧周围只有一个充满数字的序列容器,您也可以将其扔给类,它会很高兴地为您计算成员资格O(N)时间。

STL 有很多方法可以做到这一点。

如果您有非常多的项目,并且想要测试给定的项目是否是此集合的成员,请使用 setunordered_set 。它们允许您分别以log n和恒定时间检查成员资格。

如果将元素保留在排序数组中,则binary_search还将log n时间内测试成员资格。

然而,对于小数组,使用 find 进行线性搜索可能会明显更快(因为没有分支(。线性搜索甚至可以在二叉搜索"跳来跳去"的时间内进行 3-8 次比较。这篇博文建议在大约 64 个项目处有一个盈亏平衡点,低于这个点,线性搜索可能会更快,尽管这显然取决于 STL 实现、编译器优化和架构的分支预测。

如果data确实是整型或枚举类型,则可以使用switch

switch (data) {
  case 1:
  case 2:
  case 2000:
  case 6000:
  case /* whatever other values you want */:
    act_on_the_group();
    break;
  default:
    act_on_not_the_group();
    break;
}

使用 std::initializer_list 的答案很好,但我想添加另一个可能的解决方案,这正是您以类型安全和现代方式尝试使用该 C 可变参数的解决方案: 使用 C++11 可变参数模板

template<typename... NUMBERS>
bool any_equal( const foo& f , NUMBERS&&... numbers )
{
    auto unpacked = { numbers... };
    return std::find( std::begin( unpacked ) , std::end( unpacked ) , f.data ) 
           != std::end( unpacked );
};

当然,这仅在传递的所有值都属于同一类型时才有效。如果不是初始值设定项列表,则无法推断或初始化unpacked

然后:

bool equals = any_equal( f , 1,2,3,4,5 );

编辑:这是一个are_same元函数,以确保传递的所有数字都是相同的类型:

template<typename HEAD , typename... TAIL>
struct are_same : public and_op<std::is_same<HEAD,TAIL>::value...>
{};

其中and_op执行 n 元逻辑和:

template<bool HEAD , bool... TAIL>
struct and_op : public std::integral_constant<bool,HEAD && and_op<TAIL...>::value>
{};
template<>
struct and_op<> : public std::true_type
{};

这使得以简单的方式强制使用相同类型的数字成为可能:

template<typename... NUMBERS>
bool any_equal( const foo& f , NUMBERS&&... numbers )
{
    static_assert( all_same<NUMBERS...>::value , 
                   "ERROR: You should use numbers of the same type" );

    auto unpacked = { numbers... };
    return std::find( std::begin( unpacked ) , std::end( unpacked ) , f.data ) 
           != std::end( unpacked );
};

任何优化都将取决于被比较的数字集的属性。

如果有明确的上限,则可以使用 std::bitset 。测试成员资格(即索引到位集,其行为类似于数组(是 O(1(,实际上是一些快速指令。这通常是最好的解决方案,达到数百的限制,尽管根据应用的不同,数百万可能是实用的。

它并不漂亮,但这应该有效:

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; 
}

等等。这将为您提供所需的确切语法。但是,如果您追求的是完全可变数量的参数,那么向量或 std::initalizer 列表可能是要走的路:

请参阅:http://en.cppreference.com/w/cpp/utility/initializer_list

此示例显示了它的实际操作:

#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;
}

有人有更好的主意吗?感谢分享!

有一个标准的藻类:

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);

编辑:(全部在一个地方(:

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});

编辑:

我更喜欢向量的接口,而不是初始值设定项列表,因为它更强大:

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

接口(通过使用向量(不限制您定义值,您可以在其中调用 is_equal_to_one_of_these

set是一个

不错的选择,但如果你真的想自己动手,initializer_list很方便:

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

使用是微不足道的:

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

它是O(n(你,设置/无序更快

我建议使用标准容器,如 std::vector,但这仍然意味着最坏情况下运行时的线性复杂性 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"

如果数据 num1, .. num6 介于 0 和 31 之间,则可以使用

int match = ((1<<num1) | (1<<num2) | ... | (1 << num6));
if( ( (1 << data) & match ) != 0 ) ...

如果 num1 到 num6 是常量,编译器将在编译时计算匹配项。