一堂课:
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;
}
这段代码将为我完成这项工作。但每次我使用这种方法时,我都必须计算参数并将其传入。
有没有人有更好的主意?
答案 0 :(得分:46)
最简单的方法是使用一对迭代器在in()
周围编写一个名为std::find
的成员函数包装器,以查找有问题的数据。我为那个
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()
迭代器。 private
帮助器的另外两个detail_in()
重载执行一些标记调度:
decltype(c.find(data), bool())
的SFINAE技巧的一个重载,如果所讨论的容器c
没有成员函数find()
,则会从重载集中删除否则返回bool
(这是通过滥用decltype
内的逗号运算符来实现的)begin()
和end()
迭代器和委托给原始in()
两个迭代器因为detail_in()
帮助器的标签形成了继承层次结构(非常类似于标准迭代器标记),所以第一个重载将匹配关联容器std::set
和std::unordered_set
及其多个-cousins。所有其他容器(包括C阵列,std::array
,std::vector
和std::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";
}
Live Example - 所有可能容器的 - 为这两个数字集打印1和0.
std::initializer_list
重载的用例适用于小集的数据的成员测试,您在调用代码时明确地写出。它具有O(N)
复杂度,但避免了任何堆分配。
对于像大集的成员资格测试这样的重要任务,您可以将数字存储在关联容器中,例如std::set
或其multi_set
或{{1}表兄弟。这将在存储这些数字时进入堆,但具有unordered_set
或甚至O(log N)
查找复杂性。
但是,如果你碰巧只有一个数字序列容器,你也可以把它扔到课堂上,它会很乐意在O(1)
时间为你计算会员资格。
答案 1 :(得分:19)
使用STL有很多方法可以做到这一点。
如果您有非常多的商品,并且想要测试您的商品是否属于此套装的成员,请使用set
或unordered_set
。它们允许您分别检查log n
和常量时间的成员资格。
如果您将元素保留在已排序的数组中,那么binary_search
也会在log n
时间内测试成员资格。
对于小型阵列,linear search
可能会明显加快(因为没有分支)。 A linear search might even do 3-8 comparisons in the time it takes the binary search to 'jump around'。 This blog post建议在接近64个项目上有一个收支平衡点,低于该点,线性搜索可能会更快,但这显然取决于STL实现,编译器优化和您的架构的分支预测。 / p>
答案 2 :(得分:10)
如果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;
}
答案 3 :(得分:9)
使用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-ary逻辑和
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 );
};
答案 4 :(得分:6)
任何优化都将取决于与之比较的数字集的属性。
如果有明确的上限,您可以使用std::bitset
。测试成员资格(即索引到bitset,其行为类似于数组)是O(1),实际上是一些快速指令。这通常是达到数百个限制的最佳解决方案,尽管取决于应用,数百万可能是实用的。
答案 5 :(得分:4)
它不漂亮,但这应该有效:
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;
}
答案 6 :(得分:4)
有没有人有更好的主意?谢谢分享!
有一个标准的算法:
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
。
答案 7 :(得分:3)
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),设置/无序更快
答案 8 :(得分:2)
我建议使用像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"
答案 9 :(得分:1)
如果data,num1,.. num6介于0和31之间,则可以使用
int match = ((1<<num1) | (1<<num2) | ... | (1 << num6));
if( ( (1 << data) & match ) != 0 ) ...
如果num1到num6是常量,编译器将在编译时计算匹配。