Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple way to check a large number of similar conditions?

Tags:

c++

I am working on a game, and I am finding myself very often checking that certain quantities are in the bounds of the indexes accepted by the vector that represents my world:

if(a >= 0 && a < 16 && b >= 0 && b < 16 && c >= 0 && c < 16 && 
   d >= 0 && d < 16 && e >= 0 && e < 16)
{
    //do things with vector[a][b][c][d][e]
} 

I often have to check even more conditions than this. Is there a way that I can make these checks more concise and/or easier to read?

Alternatively, is there a way that I can avoid doing the checks entirely? The vector is 16x16x16x16x16; can I make it so that if I were to give it a 16 as an index, it would do nothing rather than segfault?

like image 475
Kai Schmidt Avatar asked Aug 04 '15 16:08

Kai Schmidt


6 Answers

You could write a variadic check function:

bool check(int a) {
  return 0 <= a && a < 16;
}   

template<typename... Args>
bool check(int a, Args... args) {
  return check(a) && check(args...);
}

You can use it like check(a, b, c, d, e, ...). It also has the advantage of being able to take any number of conditions.

Here's a demo

like image 144
yizzlez Avatar answered Nov 20 '22 02:11

yizzlez


Here's a compact and efficient way to do the check. It assumes two's complement arithmetic.

bool IsInBounds(int a, int b, int c, int d, int e)
{
    // Make sure only bits 0-3 are set (i.e. all values are 0-15)
    return ((a | b | c | d | e) & ~0xf) == 0;
}

This works by noting that all values outside the 0-15 range all have a bit set that isn't one of the four least significant ones, and all values inside the range don't.

Of course it's only worth using this sort of optimization if the gains in efficiency outweigh the loss of code readability.

like image 32
Adam Avatar answered Nov 20 '22 02:11

Adam


The point of functions is reusability. If you find yourself writing certain long expressions or groups of statements repeatedly, it might be time to refactor it out.

In this case, I would write a simple function to do the bounds checking:

bool isInBounds(int a, int b, int c, int d, int e)
{
     return a >= 0 && a < 16 &&
            b >= 0 && b < 16 &&
            c >= 0 && c < 16 && 
            d >= 0 && d < 16 &&
            e >= 0 && e < 16;
}

Then use it instead of your long condition:

if (isInBounds(a, b, c, d, e))
{
    // do things with array[a][b][c][d][e]
}
like image 44
Jashaszun Avatar answered Nov 20 '22 03:11

Jashaszun


You can store your variables as elements in a std::vector rather than separate variabes like this:

bool test(const std::vector<int>& values)
{
    for(auto v: values)
        if(v < 0 || v >= 16)
            return false;
    return true;
}

Alternatively if you are using C++11 or later you can use std::all_of:

if(std::all_of(std::begin(values), std::end(values),
    [](int i){ return i >= 0 && i < 16; }))
{
    // do stuff with values
}

In that case you may also be able to use a std::array.

like image 26
Galik Avatar answered Nov 20 '22 03:11

Galik


You could combine the 5 integers making up your index into one std::array or your own class.

using Index5 = std::array<int, 5>;

Then you can write a function like:

bool contains(Index5 bounds, Index5 point) {
    for (Index5::size_type d = 0; d != bounds.size(); ++d) {
        if ((unsigned)point[d] > bounds[d])  // using the trick mentioned in comments
            return false;
    }
    return true;
}

Then use it like this:

auto bounds = Index5{16, 16, 16, 16, 16};
auto point = Index5{a, b, c, d, e};

if (contains(bounds, point)) {
    // do things with point
}

Generally, I would suggest using something like Index5 instead of managing five integers.

like image 22
Chris Drew Avatar answered Nov 20 '22 01:11

Chris Drew


If the quantities a, b, c, d, and e are something that occur together quite frequently and all need to stay within the bounds of your "world" (e.g. they represent the "state" of something in that world) then it might make sense to define a class whose primary purpose is to hold one "state" consisting of those five quantities.

Then make sure that if any code ever tries to store values in an object of that class that are not within the bounds, something reasonable (not a segfault) happens instead, and no out-of-bounds values are ever stored there. That way, an object of that class is safe to pass to any function that requires a, b, c, d, and e to be within bounds, and there is no need for any such function to do bounds-checking on those five values.

like image 1
David K Avatar answered Nov 20 '22 03:11

David K