...inside matrices (gotcha!).
Created on 11/22/2024, finished on 11/27/2024
Today I had a course of informatic programming (it's just C course actually). There was a strange exercise we had to do: from a matrix, create a new one telling for each cell how many neighbors which share the same value they have.
Easy at a quick glance, but harder when thinking about edge cases.
First, I took a paper, because it's always easier for me to visualize by writing and drawing.
There are four edge cases concerning the analyzed cell :
-
It is on the first line.
-
It is on the first column.
-
It is on the last line.
-
It is on the last column.
Four more edge cases happen when you think about corners.
So I used the rough way : checking each edge case, and adding one thanks to boolean results.
I define this algorithm inside a function declared as this : size_t countNeighbors(int **matrix, size_t m, size_t n, size_t i, size_t j)
.
m
corresponds to rows and
n
to columns. I could have created a struct, but I already had done this
here.
size_t countNeighbors(int **matrix, size_t m, size_t n, size_t i, size_t j)
{
const int a = matrix[i][j];
const size_t up = (i != 0 && matrix[i-1][j] == a);
const size_t down = (i != m-1 && matrix[i+1][j] == a);
const size_t left = (j != 0 && matrix[i][j-1] == a);
const size_t right = (j != n-1 && matrix[i][j+1] == a);
const size_t up_left = (i != 0 && j != 0 && matrix[i-1][j-1] == a);
const size_t up_right = (i != 0 && j != n-1 && matrix[i-1][j+1] == a);
const size_t down_left = (i != m-1 && j != 0 && matrix[i+1][j-1] == a);
const size_t down_right = (i != m-1 && j != n-1 && matrix[i+1][j+1] == a);
return up + down + left + right + up_left + up_right + down_left + down_right;
}
It's definitely ugly, but it works, and is somewhat readable.
Now let's try to solve this exercise using
C++.
To do so I created a MatrixInt
class, since I didn't want to bother with templates, but I will be using std::optional
to safely get elements from the matrix.
class MatrixInt {
// Defining constructors and operators...
std::optional<int> at(int i, int j) const
{
if (0 <= i && i < m_rows && 0 <= j && j < m_columns) {
return m_elems[i][j];
}
return std::nullopt;
}
size_t countNeighborsAt(int i, int j) const
{
size_t result = 0;
const int a = m_elems[i][j];
// Actually, I didn't know this syntax until coding this program.
for (int k : {-1, 0, 1}) {
for (int l : {-1, 0, 1}) {
result += (this->at(i+k, j+l) == a);
}
}
return result - 1;
}
MatrixInt neighbors() const
{
MatrixInt result(m_rows, m_columns);
for (int i = 0; i < m_rows; ++i) {
for (int j = 0; j < m_columns; ++j) {
result.m_elems[i][j] = this->countNeighborsAt(i, j);
}
}
return result;
}
};
It might be fun if we were using a more modern version of C++ (like C++20) and making a function more pythonic.
MatrixInt neighbors() const
{
auto range = std::views::iota;
#define in :
MatrixInt result(m_rows, m_columns);
for (int i in range(0ull, m_rows)) {
for (int j in range(0ull, m_columns)) {
result.m_elems[i][j] = this->countNeighborsAt(i, j);
}
}
return result;
}
Yeah that's definitely cursed, but I think it was worth it.
That's all for today!