Matt Reynolds
Matt Reynolds

Reputation: 807

Most efficient way to compare a variable to multiple values?

A few times in my program, I've had to check if a variable was one of many options. For example

if (num = (<1 or 2 or 3>)) { DO STUFF }

I've messed around with 'OR's, but nothing seems to be right. I've tried

if (num == (1 || 2 || 3))

but it does nothing.

I'd like to conveniently distinguish between several groups. For example

if (num = (1,2,3))

else if (num = (4,5,6))

else if (num = (7,8,9))

Upvotes: 31

Views: 114866

Answers (7)

Ajit saigal
Ajit saigal

Reputation: 11

float n;
if (n<1) exit(0);  
if (n / 3 <= 1)  
   // within 1, 2, 3
else if (n / 3 <= 2)  
   // within 4, 5, 6  
else if (n / 3 <= 3)  
   // within 7, 8, 9

Upvotes: -2

Nikos C.
Nikos C.

Reputation: 51842

Here's a way in C++11, using std::initializer_list:

#include <algorithm>
#include <initializer_list>

template <typename T>
bool is_in(const T& v, std::initializer_list<T> lst)
{
    return std::find(std::begin(lst), std::end(lst), v) != std::end(lst);
}

with that, you can do:

if (is_in(num, {1, 2, 3})) { DO STUFF }

It is not very efficient though when not used with built-in types. int will work fine, but if you compare std::string variables for example, the produced code is just awful.

In C++17 however, you can instead use a much more efficient solution that works well with any type:

template<typename First, typename ... T>
bool is_in(First &&first, T && ... t)
{
    return ((first == t) || ...);
}

// ...

// s1, s2, s3, s4 are strings.
if (is_in(s1, s2, s3, s4)) // ...

The C++11 version would be very inefficient here, while this version should produce the same code as hand-written comparisons.

Upvotes: 75

Darren Smith
Darren Smith

Reputation: 2488

I needed to do something similar for enums. I have a variable and wish to test it against a ranges of values.

Here I've used a variadic template function. Note the specialisation for const char* type, so that is_in( my_str, "a", "b", "c") has the expected outcome for when my_str stores "a".

#include <cstring> 

template<typename T>
constexpr  bool is_in(T t, T v) {
  return t == v;
}

template<>
constexpr  bool is_in(const char* t, const char* v) {
  return std::strcmp(t,v);
}

template<typename T, typename... Args>
constexpr bool is_in(T t, T v, Args... args) {
  return  t==v || is_in(t,args...);
}

Example usage:

enum class day
{
  mon, tues, wed, thur, fri, sat, sun
};

bool is_weekend(day d)
{
  return is_in(d, day::sat, day::sun);
}

Upvotes: 5

Felix Petriconi
Felix Petriconi

Reputation: 705

I just had a similar problem and I came to these C++11 solutions:

template <class T> 
struct Is 
{ 
  T d_; 
  bool in(T a) { 
    return a == d_; 
  } 
  template <class Arg, class... Args> 
  bool in(Arg a, Args... args) { 
    return in(a) || in(args...); 
  } 
}; 

template <class T> 
Is<T> is(T d) { 
  return Is<T>{d}; 
}

Or as alternative without the recursion terminating method. Be aware that here the order of comparisons is undefined and that this does not terminate early if the first match is found. But the code is more compact.

template <class T>
struct Is {
  const T d_;
  template <class... Args>
  bool in(Args... args) {
    bool r{ false }; 
    [&r](...){}(( (r = r || d_ == args), 1)...);
    return r;
  }
};

template <class T>
Is<T> is(T d) { 
  return Is<T>{d}; 
}

So for both solutions the code would look like:

if (is(num).in(1,2,3)) {
  // do whatever needs to be done
}

Upvotes: 8

Eric Johnson
Eric Johnson

Reputation: 826

If the values you want to check are sufficiently small, you could create a bit mask of the values that you seek and then check for that bit to be set.

Suppose, you care about a couple of groups.

static const unsigned values_group_1 = (1 << 1) | (1 << 2) | (1 << 3);
static const unsigned values_group_2 = (1 << 4) | (1 << 5) | (1 << 6);
static const unsigned values_group_3 = (1 << 7) | (1 << 8) | (1 << 9);    
if ((1 << value_to_check) & values_group_1) {
  // You found a match for group 1
}
if ((1 << value_to_check) & values_group_2) {
  // You found a match for group 2
}
if ((1 << value_to_check) & values_group_3) {
  // You found a match for group 3
}

This approach works best for values that don't exceed the natural size your CPU likes to work with. This would typically be 64 in modern times, but may vary depending upon the specifics of your environment.

Upvotes: 13

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726529

You can define a set of integers, add the desired values to it, and then use the find method to see if the value in question is in the set

std::set<int> values;
// add the desired values to your set...
if (values.find(target) != values.end())
    ...

Upvotes: 7

john.pavan
john.pavan

Reputation: 950

You have to do the comparison with each value. E.g.

if (num == 1 || num == 2 || num == 3) { stuff }

You may also want to consider a switch and intentionally falling through cases (although I don't think it's the best solution for what you're stating).

switch (num) {
    case 1:
    case 2:
    case 3:
        {DO STUFF}
        break;

    default:
        //do nothing.
}

Upvotes: 10

Related Questions