C ++中的比较技巧

一类:

class foo{ public: int data; }; 

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

当然,我可以写if(data==num1|| data == num2|| data ==num3.....) ,但老实说,我写data ==时候感觉不舒服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; } 

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

有没有人有更好的主意?

简单的方法

最简单的方法是使用一对迭代器编写一个名为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>)等中写入签名template<class T> bool in(foo const&, std::initializer_list<T>)的非成员函数,并且调用它

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

困难的方式

但是,让我们完全放弃:只需添加两个public重载:

  • 一个采用一个std::initializer_list参数,用相应的初始值设定项列表参数的begin()end()迭代器来调用前一个参数。
  • 一个用于任意容器作为input,将会做一个小标签调度到一个detail_in()辅助detail_in()两个更多的private重载:
    • 一个重载做一个SFINAE技巧与尾随返回typesdecltype(c.find(data), bool()) ,如果容器c没有成员函数find() ,将被从重载集中删除,并返回否则为bool (这是通过在decltype滥用逗号运算符来实现的)
    • 一个fallback重载只需要begin()end()迭代器和委托给原始的in()取两个迭代器

因为detail_in()帮助器的标签形成一个inheritance层次结构(很像标准迭代器标签),所以第一个重载将匹配关联容器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重载的用例是用于对在调用代码中明确写出的一小组数字进行成员testing。 它具有O(N)复杂性,但是避免了任何堆分配。

对于像大型集合的成员testing这样的繁重工作,可以将数字存储在像std::setmulti_setunordered_set cousins这样的关联容器中。 这将在存储这些数字时进入堆,但是具有O(log N)或者甚至O(1)查找复杂度。

但是如果你碰巧只有一个序列容器充满了数字,你也可以把它扔到这个类中,它会在O(N)时间内愉快地为你计算成员资格。

STL有很多种方法。

如果你有一个令人难以置信的大量项目,并且你想testing你的给定项目是否是这个集合的成员,那么使用setunordered_set 。 它们允许您分别检查log n和常量时间的成员资格。

如果将元素保存在有序数组中,则binary_search也将在log ntesting成员资格。

对于小型arrays来说, 线性search可能会显着提前(因为没有分支)。 线性search甚至可以在二进制search“跳来跳去”的时间内进行3-8次比较 。 这篇博客文章指出,在64个项目中有一个盈亏平衡点,低于这个点线性search可能会更快,不过这显然取决于STL实现,编译器优化和架构的分支预测。

如果data确实是一个整数或枚举types,则可以使用一个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的答案是好的,但我想添加一个更可能的解决scheme,这正是你在哪里用types安全和现代的方式尝试使用C variadic: 使用C ++ 11 variadic模板

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

当然,这只有在所有传递的值都是相同types的情况下才有效。 如果没有unpacked的初始化程序列表不能被推断或初始化。

然后:

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

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

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

这使得可以用一种简单的方式强制使用相同types的数字:

 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 。 testing成员资格(也就是索引到bitset,其行为像一个数组),是O(1),有效地几个快速指令。 这通常是达到数百个最佳解决scheme的限度,但取决于应用程序数百万可能是实用的。

这不是很漂亮,但这应该工作:

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

等等。 这会给你以后的确切语法。 但是如果你是在完全可变的参数之后,那么vector或者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; } 

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

有一个标准的algitrithm:

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

编辑:

我更喜欢接口的vector,而不是初始化列表,因为它更强大:

 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是一个不错的select,但是如果你真的想要自己推出,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)你设置/无序更快

我build议使用像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是常量,编译器将在编译时计算匹配。