0

I have a 2D Environment where I want to get for some cells the Moore neighborhood (east, west, north and east cells). Sometimes a cell can only have 3 or 2 neighbors (if we check from a border or a corner of the grid).

Above this, I'd like to differentiate the case where there is no border (torus world, what goes up reappears down...). In such case, every cells have exactly 4 neighbors.

Hence I made 2 versions of this algorithm, one using a std::vector of coordinates, and another one using std::array of optional coordinates (I refer to coordinates as pair of short ints). See below :

version with a vector of pairs

std::vector<std::pair<uint16_t, uint16_t>>* Environment::getMooreNeighborhood(uint16_t x, uint16_t y) const{
    std::vector<std::pair<uint16_t, uint16_t>>* neighborhood = new std::vector<std::pair<uint16_t, uint16_t>>();
    std::pair<uint16_t, uint16_t> west_cell(x - 1, y);
    std::pair<uint16_t, uint16_t> east_cell(x + 1, y);
    std::pair<uint16_t, uint16_t> north_cell(x, y - 1);
    std::pair<uint16_t, uint16_t> south_cell(x, y + 1);

    if(this->torus){
        if(x == this->width - 1){
            east_cell.first = 0;
        }
        else if(x == 0){
            west_cell.first = this->width - 1;
        }
        if(y == 0){
            north_cell.second = this->height - 1;
        }
        else if(y == this->height - 1){
            south_cell.second = 0;
        }
        neighborhood->push_back(east_cell);
        neighborhood->push_back(west_cell);
        neighborhood->push_back(south_cell);
        neighborhood->push_back(north_cell);
    }else{
        if(x > 0){
            neighborhood->push_back(east_cell);
        }
        if(x < this->width - 1){
            neighborhood->push_back(west_cell);
        }
        if(y > 0){
            neighborhood->push_back(north_cell);
        }
        if(y < this->height - 1){
            neighborhood->push_back(south_cell);
        }
    }
    return neighborhood;
}

version with an array of optional pairs

std::array<std::optional<std::pair<uint16_t,uint16_t>>, 4> Environment::getMooreNeighborhood(uint16_t x, uint16_t y) const {
    std::array<std::optional<std::pair<uint16_t,uint16_t>>, 4> neighborhood;
    std::optional<std::pair<uint16_t, uint16_t>> west_cell;
    std::optional<std::pair<uint16_t, uint16_t>> east_cell;
    std::optional<std::pair<uint16_t, uint16_t>> north_cell;
    std::optional<std::pair<uint16_t, uint16_t>> south_cell;

    if(this->torus){
        if(x == this->width - 1){
            east_cell = std::pair<uint16_t, uint16_t>(0, y);
        }
        else if(x == 0){
            west_cell = std::pair<uint16_t, uint16_t>(this->width - 1, y);
        }
        if(y == 0){
            north_cell = std::pair<uint16_t, uint16_t>(x, this->height - 1);
        }
        else if(y == this->height - 1){
            south_cell = std::pair<uint16_t, uint16_t>(x, 0);
        }
    }else{
        if(x > 0){
            east_cell = std::pair<uint16_t, uint16_t>(x - 1, y);
        }
        if(x < this->width - 1){
            west_cell = std::pair<uint16_t, uint16_t>(x + 1, y);
        }
        if(y > 0){
            north_cell = std::pair<uint16_t, uint16_t>(x, y - 1);
        }
        if(y < this->height - 1){
            north_cell = std::pair<uint16_t, uint16_t>(x, y + 1);
        }
    }
    neighborhood[0] = west_cell;
    neighborhood[1] = east_cell;
    neighborhood[2] = north_cell;
    neighborhood[3] = south_cell;
    return neighborhood;
}

I have to perform this operation a very high number of times, and I don't want to shoot myself in the foot by having a bad implementation. Is one choice over the other better or am I absolutely wrong and there is another way to do efficiently ?

blaoi
  • 143
  • 4
  • Assuming that once you've found the neighbours they are all treated identically (i.e. you no longer care about the direction) then my instinct would be the vector code. But as usual you need to try both methods and time them. – john Sep 26 '20 at 06:21
  • Although I notice that you dynamically allocate the vector and you don't dynamically allocate the array, so you aren't comparing like with like. Seems to me that would make the biggest difference. I see no reason to allocate the vector. – john Sep 26 '20 at 06:23
  • @john I changed the implementation to return an object instead of a pointer and I compared the two methods for a given task. It seems that I have better performances with an array of optional pairs than with a vector. The difference is not that big, but it's still there (3min for one, 3min 15 for the other). One may want to make more performance tests to ensure this. However I noticed no changes for memory consumption for both. – blaoi Sep 26 '20 at 06:59
  • One other suggestion would be to add `neighborhood.reserve(4);` in the vector case before you start adding items to your vector. – john Sep 26 '20 at 07:32
  • well, i gave 4 in the constructor, is it the same ? – blaoi Sep 26 '20 at 07:35
  • 2
    No that's different. 4 in the constructor means you have a vector of size 4. `reserve` does not change the size of the vector but preallocates space so the vector does not have to reallocate when you start adding items. Since you're going to have a vector of size 4 most of the time and since reallocation is often a performance issue using `reserve(4)` seems a win/win. – john Sep 26 '20 at 07:37
  • That’s not the [Moore neighborhood](https://en.m.wikipedia.org/wiki/Moore_neighborhood), but the [Von Neumann](https://en.m.wikipedia.org/wiki/Von_Neumann_neighborhood). – Davis Herring Sep 26 '20 at 16:25
  • @DavisHerring Indeed you are right! Thank you for pointing this out ! – blaoi Sep 27 '20 at 13:36

0 Answers0