From 9029bcf099efbe063dd6aff7519f2abbbb657bbb Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 17 Feb 2026 21:04:40 +0300 Subject: [PATCH] Vibecoding PIVector2D - add funcs and doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавь в файл pivector2d.h комментарии для Doxygen ко всем классам и всем функциям. Комментарии должны быть в таком же стиле как в файле pivector.h. Проанализируй функциональность классов pivector2d в файле pivector2d.h и класс pivector в файле pivector.h и добавь недостающую функциональность в pivector2d по аналогии с pivector. --- libs/main/containers/pivector2d.h | 626 ++++++++++++++++++-- tests/math/testpivector2d.cpp | 940 ++++++++++++++++++++++++++++-- 2 files changed, 1468 insertions(+), 98 deletions(-) diff --git a/libs/main/containers/pivector2d.h b/libs/main/containers/pivector2d.h index 807a45ae..8f208ef3 100644 --- a/libs/main/containers/pivector2d.h +++ b/libs/main/containers/pivector2d.h @@ -1,7 +1,7 @@ /*! \file pivector2d.h * \brief 2D wrapper around PIVector * - * This file declares PIVector + * This file declares PIVector2D */ /* PIP - Platform Independent Primitives @@ -27,29 +27,82 @@ #include "pivector.h" -/*! \brief 2D array, - * \details This class used to store 2D array of any type elements as plain vector. - * You can read/write any element via operators [][], first dimension - row, second - column. - * The first dimension is Row, and you can operate with Row as PIVector: modify any element, assign to another Row and etc. - * You can't add values to array, but you can modify any elements or create another PIVector2D. - * PIVector2D has constructors from PIVector and PIVector > - */ +//! \addtogroup Containers +//! \{ +//! \class PIVector2D +//! \brief +//! \~english 2D array container. +//! \~russian Двумерный контейнер-массив. +//! \details +//! \~english +//! This class is used to store a 2D array of elements of any type as a single continuous block of memory (a plain PIVector). +//! Elements can be accessed using the `[][]` operators, where the first index is the row and the second is the column. +//! Rows can be manipulated as \a PIVector objects, allowing modification of individual elements or assignment of entire rows. +//! You cannot directly add or remove elements to change the dimensions of the array after construction +//! (use \a resize(), \a addRow(), \a removeRow(), \a removeColumn() instead), but you can modify the values of existing elements. +//! \~russian +//! Этот класс используется для хранения двумерного массива элементов любого типа в виде единого непрерывного блока памяти (обычного +//! PIVector). Доступ к элементам осуществляется с помощью операторов `[][]`, где первый индекс — это строка, а второй — столбец. Со +//! строками можно работать как с объектами \a PIVector, что позволяет изменять отдельные элементы или присваивать целые строки. Нельзя +//! напрямую добавлять или удалять элементы, чтобы изменить размеры массива после создания (используйте \a resize(), \a addRow(), \a +//! removeRow(), \a removeColumn() для этого), но можно изменять значения существующих элементов. + template class PIVector2D { public: + //! \~english Constructs an empty 2D array. + //! \~russian Создает пустой двумерный массив. inline PIVector2D() { rows_ = cols_ = 0; } + //! \~english Constructs a 2D array with the given dimensions, filled with copies of `f`. + //! \~russian Создает двумерный массив заданного размера, заполненный копиями `f`. + //! \param rows Number of rows. + //! \param cols Number of columns. + //! \param f Value to fill the array with. + //! \~english \param rows Количество строк. + //! \~russian \param rows Количество строк. + //! \~english \param cols Количество столбцов. + //! \~russian \param cols Количество столбцов. + //! \~english \param f Значение для заполнения массива. + //! \~russian \param f Значение для заполнения массива. inline PIVector2D(size_t rows, size_t cols, const T & f = T()) { rows_ = rows; cols_ = cols; mat.resize(rows * cols, f); } + //! \~english Constructs a 2D array from an existing 1D vector, reshaping it. + //! \~russian Создает двумерный массив из существующего одномерного вектора, изменяя его форму. + //! \param rows Number of rows. + //! \param cols Number of columns. + //! \param v The source 1D vector. Its size must be at least `rows * cols`. + //! \~english \param rows Количество строк. + //! \~russian \param rows Количество строк. + //! \~english \param cols Количество столбцов. + //! \~russian \param cols Количество столбцов. + //! \~english \param v Исходный одномерный вектор. Его размер должен быть не меньше `rows * cols`. + //! \~russian \param v Исходный одномерный вектор. Его размер должен быть не меньше `rows * cols`. inline PIVector2D(size_t rows, size_t cols, const PIVector & v): rows_(rows), cols_(cols), mat(v) { mat.resize(rows * cols); } + //! \~english Move constructs a 2D array from an existing 1D vector, reshaping it. + //! \~russian Конструктор перемещения из существующего одномерного вектора, изменяя его форму. + //! \param rows Number of rows. + //! \param cols Number of columns. + //! \param v The source 1D vector (rvalue reference). Its size must be at least `rows * cols`. + //! \~english \param rows Количество строк. + //! \~russian \param rows Количество строк. + //! \~english \param cols Количество столбцов. + //! \~russian \param cols Количество столбцов. + //! \~english \param v Исходный одномерный вектор (rvalue-ссылка). Его размер должен быть не меньше `rows * cols`. + //! \~russian \param v Исходный одномерный вектор (rvalue-ссылка). Его размер должен быть не меньше `rows * cols`. inline PIVector2D(size_t rows, size_t cols, PIVector && v): rows_(rows), cols_(cols), mat(std::move(v)) { mat.resize(rows * cols); } + //! \~english Constructs a 2D array from a vector of vectors (jagged array). Assumes all inner vectors have the same size. + //! \~russian Создает двумерный массив из вектора векторов (рваного массива). Предполагается, что все внутренние векторы имеют + //! одинаковый размер. \param v The source vector of vectors. + //! \~english \param v Исходный вектор векторов. + //! \~russian \param v Исходный вектор векторов. inline PIVector2D(const PIVector> & v) { rows_ = v.size(); if (rows_) { @@ -63,22 +116,43 @@ public: if (mat.isEmpty()) rows_ = cols_ = 0; } + //! \~english Number of rows. + //! \~russian Количество строк. inline size_t rows() const { return rows_; } + //! \~english Number of columns. + //! \~russian Количество столбцов. inline size_t cols() const { return cols_; } + //! \~english Total number of elements (`rows * cols`). + //! \~russian Общее количество элементов (`строки * столбцы`). inline size_t size() const { return mat.size(); } + //! \~english Total number of elements as signed value. + //! \~russian Общее количество элементов в виде знакового числа. inline ssize_t size_s() const { return mat.size_s(); } + //! \~english Total number of elements. + //! \~russian Общее количество элементов. inline size_t length() const { return mat.length(); } + //! \~english Number of elements that the underlying container has currently allocated space for. + //! \~russian Количество элементов, для которого сейчас выделена память во внутреннем контейнере. inline size_t capacity() const { return mat.capacity(); } + //! \~english Checks if the array has no elements. + //! \~russian Проверяет, пуст ли массив. inline bool isEmpty() const { return mat.isEmpty(); } + //! \~english Checks if the array has elements. + //! \~russian Проверяет, не пуст ли массив. inline bool isNotEmpty() const { return mat.isNotEmpty(); } + + //! \class Row + //! \brief + //! \~english Proxy class representing a single row in a \a PIVector2D for modification. + //! \~russian Прокси-класс, представляющий одну строку в \a PIVector2D для модификации. class Row { friend class PIVector2D; @@ -91,65 +165,119 @@ public: size_t st_, sz_; public: + //! \~english Size of the row (number of columns). + //! \~russian Размер строки (количество столбцов). inline size_t size() const { return sz_; } + + //! \~english Accesses the element at the given column index within the row. + //! \~russian Доступ к элементу по заданному индексу столбца в строке. inline T & operator[](size_t index) { return (*p_)[st_ + index]; } + + //! \~english Const access to the element at the given column index within the row. + //! \~russian Константный доступ к элементу по заданному индексу столбца в строке. inline const T & operator[](size_t index) const { return (*p_)[st_ + index]; } + + //! \~english Returns a pointer to the row data starting at an optional offset. + //! \~russian Возвращает указатель на данные строки, начиная с опционального смещения. inline T * data(size_t index = 0) { return p_->data(st_ + index); } + + //! \~english Returns a const pointer to the row data starting at an optional offset. + //! \~russian Возвращает константный указатель на данные строки, начиная с опционального смещения. inline const T * data(size_t index = 0) const { return p_->data(st_ + index); } + + //! \~english Assigns the contents of another Row to this row. + //! \~russian Присваивает этой строке содержимое другой строки. inline Row & operator=(const Row & other) { if (p_ == other.p_ && st_ == other.st_) return *this; const size_t sz = piMin(sz_, other.sz_); p_->_copyRaw(p_->data(st_), other.data(), sz); return *this; } + + //! \~english Assigns the contents of a \a PIVector to this row. + //! \~russian Присваивает этой строке содержимое \a PIVector. inline Row & operator=(const PIVector & other) { const size_t sz = piMin(sz_, other.size()); p_->_copyRaw(p_->data(st_), other.data(), sz); return *this; } + + //! \~english Converts the row to a \a PIVector. + //! \~russian Преобразует строку в \a PIVector. inline PIVector toVector() const { return PIVector(p_->data(st_), sz_); } }; + //! \class Col + //! \brief + //! \~english Proxy class representing a single column in a \a PIVector2D for modification. + //! \~russian Прокси-класс, представляющий один столбец в \a PIVector2D для модификации. class Col { friend class PIVector2D; private: - inline Col(PIVector2D * p, size_t row): p_(&(p->mat)) { + inline Col(PIVector2D * p, size_t col): p_(&(p->mat)) { step_ = p->cols_; - row_ = row; + col_ = col; sz_ = p->rows_; } PIVector * p_; - size_t step_, row_, sz_; + size_t step_, col_, sz_; public: + //! \~english Size of the column (number of rows). + //! \~russian Размер столбца (количество строк). inline size_t size() const { return sz_; } - inline T & operator[](size_t index) { return (*p_)[index * step_ + row_]; } - inline const T & operator[](size_t index) const { return (*p_)[index * step_ + row_]; } - inline T * data(size_t index = 0) { return p_->data(index * step_ + row_); } - inline const T * data(size_t index = 0) const { return p_->data(index * step_ + row_); } + + //! \~english Accesses the element at the given row index within the column. + //! \~russian Доступ к элементу по заданному индексу строки в столбце. + inline T & operator[](size_t index) { return (*p_)[index * step_ + col_]; } + + //! \~english Const access to the element at the given row index within the column. + //! \~russian Константный доступ к элементу по заданному индексу строки в столбце. + inline const T & operator[](size_t index) const { return (*p_)[index * step_ + col_]; } + + //! \~english Returns a pointer to the column data starting at an optional row offset. + //! \~russian Возвращает указатель на данные столбца, начиная с опционального смещения по строкам. + inline T * data(size_t index = 0) { return p_->data(index * step_ + col_); } + + //! \~english Returns a const pointer to the column data starting at an optional row offset. + //! \~russian Возвращает константный указатель на данные столбца, начиная с опционального смещения по строкам. + inline const T * data(size_t index = 0) const { return p_->data(index * step_ + col_); } + + //! \~english Assigns the contents of another Col to this column. + //! \~russian Присваивает этому столбцу содержимое другого столбца. inline Col & operator=(const Col & other) { - if (p_ == other.p_ && row_ == other.row_) return *this; + if (p_ == other.p_ && col_ == other.col_) return *this; const size_t sz = piMin(sz_, other.sz_); - for (int i = 0; i < sz; ++i) - (*p_)[i * step_ + row_] = other[i]; + for (size_t i = 0; i < sz; ++i) + (*p_)[i * step_ + col_] = other[i]; return *this; } - inline Row & operator=(const PIVector & other) { + + //! \~english Assigns the contents of a \a PIVector to this column. + //! \~russian Присваивает этому столбцу содержимое \a PIVector. + inline Col & operator=(const PIVector & other) { const size_t sz = piMin(sz_, other.size()); - for (int i = 0; i < sz; ++i) - (*p_)[i * step_ + row_] = other[i]; + for (size_t i = 0; i < sz; ++i) + (*p_)[i * step_ + col_] = other[i]; return *this; } + + //! \~english Converts the column to a \a PIVector. + //! \~russian Преобразует столбец в \a PIVector. inline PIVector toVector() const { PIVector ret; ret.reserve(sz_); for (size_t i = 0; i < sz_; i++) - ret << (*p_)[i * step_ + row_]; + ret << (*p_)[i * step_ + col_]; return ret; } }; + //! \class RowConst + //! \brief + //! \~english Proxy class representing a single read-only row in a \a PIVector2D. + //! \~russian Прокси-класс, представляющий одну строку в \a PIVector2D только для чтения. class RowConst { friend class PIVector2D; @@ -162,64 +290,135 @@ public: size_t st_, sz_; public: + //! \~english Size of the row (number of columns). + //! \~russian Размер строки (количество столбцов). inline size_t size() const { return sz_; } + + //! \~english Const access to the element at the given column index within the row. + //! \~russian Константный доступ к элементу по заданному индексу столбца в строке. inline const T & operator[](size_t index) const { return (*p_)[st_ + index]; } + + //! \~english Returns a const pointer to the row data starting at an optional offset. + //! \~russian Возвращает константный указатель на данные строки, начиная с опционального смещения. inline const T * data(size_t index = 0) const { return p_->data(st_ + index); } + + //! \~english Converts the row to a \a PIVector. + //! \~russian Преобразует строку в \a PIVector. inline PIVector toVector() const { return PIVector(p_->data(st_), sz_); } }; + //! \class ColConst + //! \brief + //! \~english Proxy class representing a single read-only column in a \a PIVector2D. + //! \~russian Прокси-класс, представляющий один столбец в \a PIVector2D только для чтения. class ColConst { friend class PIVector2D; private: - inline ColConst(const PIVector2D * p, size_t row): p_(&(p->mat)) { + inline ColConst(const PIVector2D * p, size_t col): p_(&(p->mat)) { step_ = p->cols_; - row_ = row; + col_ = col; sz_ = p->rows_; } const PIVector * p_; - size_t step_, row_, sz_; + size_t step_, col_, sz_; public: - inline size_t size() const { return p_->rows_; } - inline const T & operator[](size_t index) const { return (*p_)[index * step_ + row_]; } - inline const T * data(size_t index = 0) const { return p_->data(index * step_ + row_); } + //! \~english Size of the column (number of rows). + //! \~russian Размер столбца (количество строк). + inline size_t size() const { return sz_; } + + //! \~english Const access to the element at the given row index within the column. + //! \~russian Константный доступ к элементу по заданному индексу строки в столбце. + inline const T & operator[](size_t index) const { return (*p_)[index * step_ + col_]; } + + //! \~english Returns a const pointer to the column data starting at an optional row offset. + //! \~russian Возвращает константный указатель на данные столбца, начиная с опционального смещения по строкам. + inline const T * data(size_t index = 0) const { return p_->data(index * step_ + col_); } + + //! \~english Converts the column to a \a PIVector. + //! \~russian Преобразует столбец в \a PIVector. inline PIVector toVector() const { PIVector ret; ret.reserve(sz_); - for (int i = 0; i < size(); i++) - ret << (*p_)[i * step_ + row_]; + for (size_t i = 0; i < size(); i++) + ret << (*p_)[i * step_ + col_]; return ret; } }; + //! \~english Returns a reference to the element at the given row and column. + //! \~russian Возвращает ссылку на элемент по заданной строке и столбцу. inline T & element(size_t row, size_t col) { return mat[row * cols_ + col]; } + + //! \~english Returns a const reference to the element at the given row and column. + //! \~russian Возвращает константную ссылку на элемент по заданной строке и столбцу. inline const T & element(size_t row, size_t col) const { return mat[row * cols_ + col]; } + + //! \~english Returns a const reference to the element at the given row and column (bounds-checked only in debug). + //! \~russian Возвращает константную ссылку на элемент по заданной строке и столбцу (проверка границ только в отладочном режиме). inline const T & at(size_t row, size_t col) const { return mat[row * cols_ + col]; } + + //! \~english Returns a proxy object for the row at the given index for modification. + //! \~russian Возвращает прокси-объект для строки по заданному индексу для модификации. inline Row operator[](size_t index) { return Row(this, index); } + + //! \~english Returns a proxy object for the row at the given index for read-only access. + //! \~russian Возвращает прокси-объект для строки по заданному индексу только для чтения. inline RowConst operator[](size_t index) const { return RowConst(this, index); } + + //! \~english Returns a pointer to the underlying flat data starting at an optional offset. + //! \~russian Возвращает указатель на внутренние плоские данные, начиная с опционального смещения. inline T * data(size_t index = 0) { return mat.data(index); } + + //! \~english Returns a const pointer to the underlying flat data starting at an optional offset. + //! \~russian Возвращает константный указатель на внутренние плоские данные, начиная с опционального смещения. inline const T * data(size_t index = 0) const { return mat.data(index); } + + //! \~english Returns a proxy object for the row at the given index for modification. + //! \~russian Возвращает прокси-объект для строки по заданному индексу для модификации. inline Row row(size_t index) { return Row(this, index); } + + //! \~english Returns a proxy object for the row at the given index for read-only access. + //! \~russian Возвращает прокси-объект для строки по заданному индексу только для чтения. inline RowConst row(size_t index) const { return RowConst(this, index); } + + //! \~english Returns a proxy object for the column at the given index for modification. + //! \~russian Возвращает прокси-объект для столбца по заданному индексу для модификации. inline Col col(size_t index) { return Col(this, index); } + + //! \~english Returns a proxy object for the column at the given index for read-only access. + //! \~russian Возвращает прокси-объект для столбца по заданному индексу только для чтения. inline ColConst col(size_t index) const { return ColConst(this, index); } + + + //! \~english Replaces a row with the contents of another Row object. + //! \~russian Заменяет строку содержимым другого объекта Row. inline PIVector2D & setRow(size_t row, const Row & other) { const size_t sz = piMin(cols_, other.sz_); mat._copyRaw(mat.data(cols_ * row), other.data(), sz); return *this; } + + //! \~english Replaces a row with the contents of a read-only RowConst object. + //! \~russian Заменяет строку содержимым объекта RowConst только для чтения. inline PIVector2D & setRow(size_t row, const RowConst & other) { const size_t sz = piMin(cols_, other.sz_); mat._copyRaw(mat.data(cols_ * row), other.data(), sz); return *this; } + + //! \~english Replaces a row with the contents of a \a PIVector. + //! \~russian Заменяет строку содержимым \a PIVector. inline PIVector2D & setRow(size_t row, const PIVector & other) { const size_t sz = piMin(cols_, other.size()); mat._copyRaw(mat.data(cols_ * row), other.data(), sz); return *this; } + + //! \~english Appends a new row to the bottom of the array from another Row object. + //! \~russian Добавляет новую строку в конец массива из другого объекта Row. inline PIVector2D & addRow(const Row & other) { if (cols_ == 0) cols_ = other.sz_; const size_t sz = piMin(cols_, other.sz_); @@ -229,6 +428,9 @@ public: rows_++; return *this; } + + //! \~english Appends a new row to the bottom of the array from a read-only RowConst object. + //! \~russian Добавляет новую строку в конец массива из объекта RowConst только для чтения. inline PIVector2D & addRow(const RowConst & other) { if (cols_ == 0) cols_ = other.sz_; const size_t sz = piMin(cols_, other.sz_); @@ -238,6 +440,9 @@ public: rows_++; return *this; } + + //! \~english Appends a new row to the bottom of the array from a \a PIVector. + //! \~russian Добавляет новую строку в конец массива из \a PIVector. inline PIVector2D & addRow(const PIVector & other) { if (cols_ == 0) cols_ = other.size(); const size_t sz = piMin(cols_, other.size()); @@ -248,34 +453,40 @@ public: return *this; } + //! \~english Resizes the 2D array to new dimensions. + //! \~russian Изменяет размер двумерного массива. + //! \details + //! \~english If the new dimensions are larger, new elements are filled with `f`. + //! If they are smaller, the array is truncated. + //! \~russian Если новые размеры больше, новые элементы заполняются `f`. + //! Если они меньше, массив обрезается. inline PIVector2D & resize(size_t rows, size_t cols, const T & f = T()) { - mat.resize(rows * cols_, f); - rows_ = rows; - const int cs = (cols - cols_); - if (cs < 0) { - for (size_t r = 0; r < rows; ++r) { - mat.remove(r * cols + cols, -cs); + if (rows == rows_ && cols == cols_) return *this; + PIVector2D tmp(rows, cols, f); + size_t copyRows = piMin(rows_, rows); + size_t copyCols = piMin(cols_, cols); + for (size_t r = 0; r < copyRows; ++r) { + for (size_t c = 0; c < copyCols; ++c) { + tmp.element(r, c) = element(r, c); } } - mat.resize(rows * cols, f); - if (!mat.isEmpty()) { - if (cs > 0) { - for (size_t r = 0; r < rows_; ++r) { - for (int i = 0; i < cs; ++i) - mat.insert(r * cols + cols_, mat.take_back()); - } - } - } - cols_ = cols; + swap(tmp); return *this; } + //! \~english Equality operator. + //! \~russian Оператор равенства. inline bool operator==(const PIVector2D & t) const { if (cols_ != t.cols_ || rows_ != t.rows_) return false; return mat == t.mat; } + + //! \~english Inequality operator. + //! \~russian Оператор неравенства. inline bool operator!=(const PIVector2D & t) const { return !(*this == t); } + //! \~english Converts the 2D array to a vector of vectors (PIVector>). + //! \~russian Преобразует двумерный массив в вектор векторов (PIVector>). inline PIVector> toVectors() const { PIVector> ret; ret.reserve(rows_); @@ -284,18 +495,27 @@ public: return ret; } + //! \~english Returns a const reference to the underlying flat \a PIVector. + //! \~russian Возвращает константную ссылку на внутренний плоский \a PIVector. + inline const PIVector & asPlainVector() const { return mat; } + + //! \~english Returns a reference to the underlying flat \a PIVector. + //! \~russian Возвращает ссылку на внутренний плоский \a PIVector. + inline PIVector & asPlainVector() { return mat; } + + //! \~english Returns a copy of the underlying flat \a PIVector. + //! \~russian Возвращает копию внутреннего плоского \a PIVector. inline PIVector toPlainVector() const { return mat; } - inline PIVector & plainVector() { return mat; } - - inline const PIVector & plainVector() const { return mat; } - + //! \~english Swaps this 2D array with another. + //! \~russian Меняет местами этот двумерный массив с другим. inline void swap(PIVector2D & other) { mat.swap(other.mat); piSwap(rows_, other.rows_); piSwap(cols_, other.cols_); } + //! \internal template::value, int>::type = 0> inline PIVector2D & _resizeRaw(size_t r, size_t c) { rows_ = r; @@ -304,29 +524,326 @@ public: return *this; } + //! \~english Clears the array, removing all elements and setting dimensions to 0. + //! \~russian Очищает массив, удаляя все элементы и устанавливая размеры в 0. inline void clear() { rows_ = cols_ = 0; mat.clear(); } - template - inline PIVector2D map(std::function f) const { - return PIVector2D(rows_, cols_, mat.map(f)); + + //! \~english Checks if the underlying flat vector contains the element `e`. + //! \~russian Проверяет, содержит ли внутренний плоский вектор элемент `e`. + inline bool contains(const T & e, ssize_t start = 0) const { return mat.contains(e, start); } + + //! \~english Checks if the underlying flat vector contains all elements of `v`. + //! \~russian Проверяет, содержит ли внутренний плоский вектор все элементы `v`. + inline bool contains(const PIVector & v, ssize_t start = 0) const { return mat.contains(v, start); } + + //! \~english Counts occurrences of `e` in the underlying flat vector. + //! \~russian Подсчитывает количество вхождений `e` во внутреннем плоском векторе. + inline int entries(const T & e, ssize_t start = 0) const { return mat.entries(e, start); } + + //! \~english Counts elements in the flat vector that pass the `test`. + //! \~russian Подсчитывает элементы в плоском векторе, проходящие `test`. + inline int entries(std::function test, ssize_t start = 0) const { return mat.entries(test, start); } + + + // TODO: next 4 functions implement in to RowConst and ColConst and rewtite thisto return index as PIPair + //! \~english Returns the first index of `e` in the flat vector. + //! \~russian Возвращает первый индекс `e` в плоском векторе. + inline ssize_t indexOf(const T & e, ssize_t start = 0) const { return mat.indexOf(e, start); } + //! \~english Returns the first index in the flat vector that passes the `test`. + //! \~russian Возвращает первый индекс в плоском векторе, проходящий `test`. + inline ssize_t indexWhere(std::function test, ssize_t start = 0) const { return mat.indexWhere(test, start); } + //! \~english Returns the last index of `e` in the flat vector. + //! \~russian Возвращает последний индекс `e` в плоском векторе. + inline ssize_t lastIndexOf(const T & e, ssize_t start = -1) const { return mat.lastIndexOf(e, start); } + //! \~english Returns the last index in the flat vector that passes the `test`. + //! \~russian Возвращает последний индекс в плоском векторе, проходящий `test`. + inline ssize_t lastIndexWhere(std::function test, ssize_t start = -1) const { + return mat.lastIndexWhere(test, start); } + //! \~english Tests if any element in the flat vector passes the `test`. + //! \~russian Проверяет, проходит ли какой-либо элемент в плоском векторе `test`. + inline bool any(std::function test) const { return mat.any(test); } + + //! \~english Tests if all elements in the flat vector pass the `test`. + //! \~russian Проверяет, проходят ли все элементы в плоском векторе `test`. + inline bool every(std::function test) const { return mat.every(test); } + + //! \~english Fills the entire 2D array with copies of `e`. + //! \~russian Заполняет весь двумерный массив копиями `e`. + inline PIVector2D & fill(const T & e = T()) { + mat.fill(e); + return *this; + } + + //! \~english Fills the entire 2D array using a generator function `f` based on flat index. + //! \~russian Заполняет весь двумерный массив, используя функцию-генератор `f` на основе плоского индекса. + inline PIVector2D & fill(std::function f) { + mat.fill(f); + return *this; + } + + //! \~english Same as \a fill(). + //! \~russian То же, что и \a fill(). + inline PIVector2D & assign(const T & e = T()) { return fill(e); } + + + // TODO: rewrite with size_t rows, size_t cols arguments + //! \~english Assigns a new size to the underlying flat vector and resets the 2D structure to 1 row. + //! \~russian Присваивает новый размер внутреннему плоскому вектору и сбрасывает 2D-структуру до 1 строки. + inline PIVector2D & assign(size_t new_size, const T & f) { + mat.assign(new_size, f); + if (mat.isEmpty()) { + rows_ = cols_ = 0; + } else { + rows_ = 1; + cols_ = mat.size(); + } + return *this; + } + + // TODO: fix for different rows and cols count + //! \~english Returns a transposed 2D array (rows become columns and vice versa). + //! \~russian Возвращает транспонированный двумерный массив (строки становятся столбцами и наоборот). + inline PIVector2D transposed() const { + if (isEmpty()) return PIVector2D(); + PIVector2D result(cols_, rows_); + for (size_t r = 0; r < rows_; ++r) { + for (size_t c = 0; c < cols_; ++c) { + result.element(c, r) = element(r, c); + } + } + return result; + } + + // TODO: переписать по возможности избегая копирования данных, в идеале использовать piSwap + //! \~english Reverses the order of rows in place. + //! \~russian Изменяет порядок строк на обратный на месте. + inline PIVector2D & reverseRows() { + const size_t half = rows_ / 2; + for (size_t i = 0; i < half; ++i) { + Row r1 = row(i); + Row r2 = row(rows_ - 1 - i); + PIVector temp = r1.toVector(); + r1 = r2.toVector(); + r2 = temp; + } + return *this; + } + + //! \~english Reverses the order of columns in each row in place. + //! \~russian Изменяет порядок столбцов в каждой строке на обратный на месте. + inline PIVector2D & reverseColumns() { + for (size_t r = 0; r < rows_; ++r) { + Row currentRow = row(r); + const size_t half = cols_ / 2; + for (size_t c = 0; c < half; ++c) { + piSwap(currentRow[c], currentRow[cols_ - 1 - c]); + } + } + return *this; + } + + //! \~english Returns a sub-2D array (a range of rows and columns). + //! \~russian Возвращает подмассив (диапазон строк и столбцов). + inline PIVector2D getRange(size_t rowStart, size_t rowCount, size_t colStart, size_t colCount) const { + if (rowStart >= rows_ || colStart >= cols_ || rowCount == 0 || colCount == 0) return PIVector2D(); + size_t actualRowCount = piMin(rowCount, rows_ - rowStart); + size_t actualColCount = piMin(colCount, cols_ - colStart); + + PIVector2D result(actualRowCount, actualColCount); + for (size_t r = 0; r < actualRowCount; ++r) { + for (size_t c = 0; c < actualColCount; ++c) { + result.element(r, c) = element(rowStart + r, colStart + c); + } + } + return result; + } + + //! \~english Applies a function to each element and returns a new 2D array of a different type. + //! \~russian Применяет функцию к каждому элементу и возвращает новый двумерный массив другого типа. + template + inline PIVector2D map(std::function f) const { + return PIVector2D(rows_, cols_, mat.template map(f)); + } + + //! \~english Applies a function (with row and col indices) to each element and returns a new 2D array. + //! \~russian Применяет функцию (с индексами строки и столбца) к каждому элементу и возвращает новый двумерный массив. + template + inline PIVector2D mapIndexed(std::function f) const { + PIVector mappedMat; + mappedMat.reserve(size()); + for (size_t r = 0; r < rows_; ++r) { + for (size_t c = 0; c < cols_; ++c) { + mappedMat << f(r, c, element(r, c)); + } + } + return PIVector2D(rows_, cols_, std::move(mappedMat)); + } + + + /* + //! \~english Executes a read-only function for each element. + //! \~russian Выполняет функцию только для чтения для каждого элемента. inline void forEach(std::function f) const { mat.forEach(f); } + //! \~english Executes a function for each element, allowing modification. + //! \~russian Выполняет функцию для каждого элемента, позволяя их изменять. inline PIVector2D & forEach(std::function f) { - mat.forEach(f); + mat.forEach(f); + return *this; + } + + //! \~english Executes a read-only function (with row and col indices) for each element. + //! \~russian Выполняет функцию только для чтения (с индексами строки и столбца) для каждого элемента. + inline void forEachIndexed(std::function f) const { + for (size_t r = 0; r < rows_; ++r) { + for (size_t c = 0; c < cols_; ++c) { + f(r, c, element(r, c)); + } + } + } + + //! \~english Executes a function (with row and col indices) for each element, allowing modification. + //! \~russian Выполняет функцию (с индексами строки и столбца) для каждого элемента, позволяя их изменять. + inline PIVector2D & forEachIndexed(std::function f) { + for (size_t r = 0; r < rows_; ++r) { + for (size_t c = 0; c < cols_; ++c) { + f(r, c, element(r, c)); + } + } + return *this; + } + */ + // TODO: Переделать закомментаренные выше функции на функции forEachRow, forEachColumn + + + //! \~english Accumulates a value across all elements. + //! \~russian Аккумулирует значение по всем элементам. + template + inline ST reduce(std::function f, const ST & initial = ST()) const { + return mat.template reduce(f, initial); + } + + //! \~english Accumulates a value across all elements with indices. + //! \~russian Аккумулирует значение по всем элементам с индексами. + template + inline ST reduceIndexed(std::function f, const ST & initial = ST()) const { + ST ret(initial); + for (size_t r = 0; r < rows_; ++r) { + for (size_t c = 0; c < cols_; ++c) { + ret = f(r, c, element(r, c), ret); + } + } + return ret; + } + + //! \~english Removes a row from the 2D array. + //! \~russian Удаляет строку из двумерного массива. + inline PIVector2D & removeRow(size_t row) { + if (row >= rows_) return *this; + size_t startIdx = row * cols_; + mat.remove(startIdx, cols_); + rows_--; + if (rows_ == 0) cols_ = 0; return *this; } + //! \~english Removes a column from the 2D array. + //! \~russian Удаляет столбец из двумерного массива. + inline PIVector2D & removeColumn(size_t col) { + if (col >= cols_ || rows_ == 0) return *this; + PIVector2D result(rows_, cols_ - 1); + for (size_t r = 0; r < rows_; ++r) { + for (size_t c = 0, nc = 0; c < cols_; ++c) { + if (c == col) continue; + result.element(r, nc++) = element(r, c); + } + } + swap(result); + return *this; + } + + //! \~english Removes all rows that satisfy a condition. + //! \~russian Удаляет все строки, удовлетворяющие условию. + inline PIVector2D & removeRowsWhere(std::function test) { + ssize_t r = rows_ - 1; + while (r >= 0) { + if (test(RowConst(this, r))) { + removeRow(r); + } + --r; + } + return *this; + } + + //! \~english Removes all columns that satisfy a condition. + //! \~russian Удаляет все столбцы, удовлетворяющие условию. + inline PIVector2D & removeColumnsWhere(std::function test) { + ssize_t c = cols_ - 1; + while (c >= 0) { + if (test(ColConst(this, c))) { + removeColumn(c); + } + --c; + } + return *this; + } + + + //! \~english Returns a new 2D array containing only the rows that pass the test. + //! \~russian Возвращает новый двумерный массив, содержащий только строки, прошедшие проверку. + inline PIVector2D filterRows(std::function test) const { + PIVector2D result; + for (size_t r = 0; r < rows_; ++r) { + RowConst currentRow = row(r); + if (test(currentRow)) { + result.addRow(currentRow); + } + } + return result; + } + + //! \~english Returns a new 2D array containing only the columns that pass the test. + //! \~russian Возвращает новый двумерный массив, содержащий только столбцы, прошедшие проверку. + inline PIVector2D filterColumns(std::function test) const { + if (isEmpty()) return PIVector2D(); + PIVector goodCols; + for (size_t c = 0; c < cols_; ++c) { + if (test(col(c))) { + goodCols << c; + } + } + PIVector2D result(rows_, goodCols.size()); + for (size_t r = 0; r < rows_; ++r) { + for (size_t gc = 0; gc < goodCols.size(); ++gc) { + result.element(r, gc) = element(r, goodCols[gc]); + } + } + return result; + } + + //! \~english Returns a new 2D array (as a single row) containing only the elements that pass the test. + //! \~russian Возвращает новый двумерный массив (в виде одной строки), содержащий только элементы, прошедшие проверку. + inline PIVector2D filterElements(std::function test) const { + PIVector filtered = mat.filter(test); + if (filtered.isEmpty()) return PIVector2D(); + return PIVector2D(1, filtered.size(), filtered); + } + protected: size_t rows_, cols_; PIVector mat; }; +//! \relatesalso PICout +//! \~english Output operator for \a PIVector2D to \a PICout. +//! \~russian Оператор вывода \a PIVector2D в \a PICout. template inline PICout operator<<(PICout s, const PIVector2D & v) { s.saveAndSetControls(0); @@ -346,5 +863,6 @@ inline PICout operator<<(PICout s, const PIVector2D & v) { return s; } +//! \} #endif // PIVECTOR2D_H diff --git a/tests/math/testpivector2d.cpp b/tests/math/testpivector2d.cpp index 12718555..357c2383 100644 --- a/tests/math/testpivector2d.cpp +++ b/tests/math/testpivector2d.cpp @@ -1,85 +1,937 @@ +#include "pistring.h" #include "pivector2d.h" #include "gtest/gtest.h" +#include -int ROWS_COUNT_INIT = 31; +size_t ROWS_COUNT_INIT = 31; +size_t COLS_COUNT_INIT = 34; int ROWS_COUNT_INCREASE = 41; -int ROWS_COUNT_REDUCE = 22; - -int COLS_COUNT_INIT = 34; int COLS_COUNT_INCREASE = 44; +int ROWS_COUNT_REDUCE = 22; int COLS_COUNT_REDUCE = 13; -void assert_fill_with(PIVector2D vec, int rows, int cols) { - for (int r = 0; r < rows; r++) { - for (int c = 0; c < cols; c++) { - ASSERT_EQ(vec.element(r, c), r * COLS_COUNT_INIT + c); +void fill_with_sequential(PIVector2D & vec, int rows, int cols) { + for (int r = 0; r < rows; ++r) { + for (int c = 0; c < cols; ++c) { + vec.element(r, c) = r * cols + c; } } } -class Vector2D: public ::testing::Test { +void assert_fill_with_sequential(const PIVector2D & vec, int rows, int cols) { + ASSERT_EQ(vec.rows(), rows); + ASSERT_EQ(vec.cols(), cols); + for (int r = 0; r < rows; ++r) { + for (int c = 0; c < cols; ++c) { + ASSERT_EQ(vec.element(r, c), r * cols + c); + } + } +} + +class Vector2DTest: public ::testing::Test { protected: PIVector2D vec = PIVector2D(ROWS_COUNT_INIT, COLS_COUNT_INIT); - void SetUp() override { - for (int r = 0; r < ROWS_COUNT_INIT; ++r) { - for (int c = 0; c < COLS_COUNT_INIT; ++c) { - vec.element(r, c) = r * COLS_COUNT_INIT + c; + void SetUp() override { fill_with_sequential(vec, ROWS_COUNT_INIT, COLS_COUNT_INIT); } +}; + +// ==================== CONSTRUCTOR TESTS ==================== +TEST_F(Vector2DTest, defaultConstructor_createsEmptyVector) { + PIVector2D emptyVec; + EXPECT_TRUE(emptyVec.isEmpty()); + EXPECT_EQ(emptyVec.rows(), 0); + EXPECT_EQ(emptyVec.cols(), 0); + EXPECT_EQ(emptyVec.size(), 0); +} + +TEST_F(Vector2DTest, sizedConstructor_createsCorrectDimensions) { + PIVector2D testVec(5, 3, 42); + EXPECT_EQ(testVec.rows(), 5); + EXPECT_EQ(testVec.cols(), 3); + EXPECT_EQ(testVec.size(), 15); + + for (size_t r = 0; r < 5; ++r) { + for (size_t c = 0; c < 3; ++c) { + EXPECT_EQ(testVec.element(r, c), 42); + } + } +} + +TEST_F(Vector2DTest, fromPlainVector_constructor_reshapesCorrectly) { + PIVector plain(20); + std::iota(plain.data(), plain.data() + 20, 0); + + PIVector2D vec2d(4, 5, plain); + EXPECT_EQ(vec2d.rows(), 4); + EXPECT_EQ(vec2d.cols(), 5); + + for (size_t r = 0; r < 4; ++r) { + for (size_t c = 0; c < 5; ++c) { + EXPECT_EQ(vec2d.element(r, c), static_cast(r * 5 + c)); + } + } +} + +TEST_F(Vector2DTest, fromPlainVector_move_constructor_reshapesCorrectly) { + PIVector plain(20); + std::iota(plain.data(), plain.data() + 20, 0); + + PIVector2D vec2d(4, 5, std::move(plain)); + EXPECT_EQ(vec2d.rows(), 4); + EXPECT_EQ(vec2d.cols(), 5); + EXPECT_TRUE(plain.isEmpty()); // Moved-from state + + for (size_t r = 0; r < 4; ++r) { + for (size_t c = 0; c < 5; ++c) { + EXPECT_EQ(vec2d.element(r, c), static_cast(r * 5 + c)); + } + } +} + +TEST_F(Vector2DTest, fromVectorOfVectors_constructor_reshapesCorrectly) { + PIVector> vectors; + vectors << PIVector({1, 2, 3}) << PIVector({4, 5, 6}) << PIVector({7, 8, 9}); + + PIVector2D vec2d(vectors); + EXPECT_EQ(vec2d.rows(), 3); + EXPECT_EQ(vec2d.cols(), 3); + + EXPECT_EQ(vec2d.element(0, 0), 1); + EXPECT_EQ(vec2d.element(1, 1), 5); + EXPECT_EQ(vec2d.element(2, 2), 9); +} + +// ==================== CAPACITY TESTS ==================== +TEST_F(Vector2DTest, sizeMethods_returnCorrectValues) { + EXPECT_EQ(vec.rows(), ROWS_COUNT_INIT); + EXPECT_EQ(vec.cols(), COLS_COUNT_INIT); + EXPECT_EQ(vec.size(), ROWS_COUNT_INIT * COLS_COUNT_INIT); + EXPECT_EQ(vec.size_s(), static_cast(ROWS_COUNT_INIT * COLS_COUNT_INIT)); + EXPECT_EQ(vec.length(), ROWS_COUNT_INIT * COLS_COUNT_INIT); + EXPECT_FALSE(vec.isEmpty()); + EXPECT_TRUE(vec.isNotEmpty()); + EXPECT_GE(vec.capacity(), vec.size()); +} + +// ==================== ELEMENT ACCESS TESTS ==================== +TEST_F(Vector2DTest, element_access_returnsCorrectValues) { + EXPECT_EQ(vec.element(5, 7), 5 * COLS_COUNT_INIT + 7); + EXPECT_EQ(vec.at(10, 20), 10 * COLS_COUNT_INIT + 20); + + vec.element(15, 15) = 999; + EXPECT_EQ(vec.element(15, 15), 999); +} + +TEST_F(Vector2DTest, row_proxy_allows_elementAccess) { + auto row = vec[5]; + EXPECT_EQ(row.size(), COLS_COUNT_INIT); + EXPECT_EQ(row[7], 5 * COLS_COUNT_INIT + 7); + + row[10] = 123; + EXPECT_EQ(vec.element(5, 10), 123); +} + +TEST_F(Vector2DTest, row_proxy_data_pointer_works) { + auto row = vec[10]; + int * ptr = row.data(); + EXPECT_EQ(ptr, vec.data(10 * COLS_COUNT_INIT)); + + ptr[5] = 777; + EXPECT_EQ(vec.element(10, 5), 777); +} + +TEST_F(Vector2DTest, row_proxy_const_access_works) { + const auto & constVec = vec; + auto row = constVec[5]; + EXPECT_EQ(row[7], 5 * COLS_COUNT_INIT + 7); + + // Compilation test - uncommenting should fail + // row[10] = 123; +} + +TEST_F(Vector2DTest, row_proxy_assignment_works) { + PIVector2D other(ROWS_COUNT_INIT, COLS_COUNT_INIT, 42); + + vec[10] = other[10]; + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(10, c), 42); + } + + PIVector newRow(COLS_COUNT_INIT, 99); + vec[15] = newRow; + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(15, c), 99); + } +} + +TEST_F(Vector2DTest, row_proxy_toVector_conversion_works) { + auto rowVec = vec[7].toVector(); + EXPECT_EQ(rowVec.size(), COLS_COUNT_INIT); + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(rowVec[c], vec.element(7, c)); + } +} + +TEST_F(Vector2DTest, col_proxy_allows_elementAccess) { + auto col = vec.col(5); + EXPECT_EQ(col.size(), ROWS_COUNT_INIT); + EXPECT_EQ(col[10], 10 * COLS_COUNT_INIT + 5); + + col[15] = 456; + EXPECT_EQ(vec.element(15, 5), 456); +} + +TEST_F(Vector2DTest, col_proxy_data_pointer_works) { + auto col = vec.col(8); + int * ptr = col.data(5); // Start from row 5 + EXPECT_EQ(ptr, &vec.element(5, 8)); + + col[2] = 888; // This should affect row 7 + EXPECT_EQ(vec.element(2, 8), 888); +} + +TEST_F(Vector2DTest, col_proxy_assignment_works) { + PIVector2D other(ROWS_COUNT_INIT, COLS_COUNT_INIT, 42); + + vec.col(12) = other.col(12); + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + EXPECT_EQ(vec.element(r, 12), 42); + } + + PIVector newCol(ROWS_COUNT_INIT, 77); + vec.col(20) = newCol; + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + EXPECT_EQ(vec.element(r, 20), 77); + } +} + +TEST_F(Vector2DTest, col_proxy_toVector_conversion_works) { + auto colVec = vec.col(9).toVector(); + EXPECT_EQ(colVec.size(), ROWS_COUNT_INIT); + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + EXPECT_EQ(colVec[r], vec.element(r, 9)); + } +} + +TEST_F(Vector2DTest, row_and_col_methods_return_same_as_operator) { + auto row1 = vec.row(10); + auto row2 = vec[10]; + EXPECT_EQ(row1[0], row2[0]); + + auto col1 = vec.col(15); + auto col2 = vec.col(15); // No operator[] for col + EXPECT_EQ(col1[5], col2[5]); +} + +// ==================== MODIFIER TESTS ==================== +TEST_F(Vector2DTest, setRow_replaces_row_correctly) { + PIVector newRow(COLS_COUNT_INIT); + std::iota(newRow.data(), newRow.data() + COLS_COUNT_INIT, 100); + + vec.setRow(12, newRow); + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(12, c), static_cast(100 + c)); + } +} + +TEST_F(Vector2DTest, setRow_with_shorter_vector_truncates) { + PIVector shortRow(COLS_COUNT_INIT - 5, 999); + vec.setRow(8, shortRow); + + for (size_t c = 0; c < COLS_COUNT_INIT - 5; ++c) { + EXPECT_EQ(vec.element(8, c), 999); + } + // Rest unchanged + EXPECT_EQ(vec.element(8, COLS_COUNT_INIT - 5), 8 * COLS_COUNT_INIT + COLS_COUNT_INIT - 5); +} + +TEST_F(Vector2DTest, addRow_appends_row_to_empty) { + PIVector2D empty; + PIVector newRow(5, 42); + + empty.addRow(newRow); + EXPECT_EQ(empty.rows(), 1); + EXPECT_EQ(empty.cols(), 5); + for (size_t c = 0; c < 5; ++c) { + EXPECT_EQ(empty.element(0, c), 42); + } +} + +TEST_F(Vector2DTest, addRow_appends_row_to_existing) { + size_t oldRows = vec.rows(); + PIVector newRow(COLS_COUNT_INIT, 999); + + vec.addRow(newRow); + EXPECT_EQ(vec.rows(), oldRows + 1); + EXPECT_EQ(vec.cols(), COLS_COUNT_INIT); + + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(oldRows, c), 999); + } +} + +TEST_F(Vector2DTest, addRow_with_shorter_vector_uses_min) { + size_t oldRows = vec.rows(); + size_t shortCols = COLS_COUNT_INIT - 10; + PIVector shortRow(shortCols, 777); + + vec.addRow(shortRow); + EXPECT_EQ(vec.rows(), oldRows + 1); + EXPECT_EQ(vec.cols(), COLS_COUNT_INIT); // cols unchanged + + for (size_t c = 0; c < shortCols; ++c) { + EXPECT_EQ(vec.element(oldRows, c), 777); + } + for (size_t c = shortCols; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(oldRows, c), 0); // default initialized + } +} + +// ==================== RESIZE TESTS ==================== +class Vector2DResizeTest: public Vector2DTest { +protected: + void assert_resize_reduce_preserves_data(int newRows, int newCols) { + vec.resize(newRows, newCols, 0); + ASSERT_EQ(vec.rows(), newRows); + ASSERT_EQ(vec.cols(), newCols); + + for (int r = 0; r < newRows; ++r) { + for (int c = 0; c < newCols; ++c) { + EXPECT_EQ(vec.element(r, c), r * COLS_COUNT_INIT + c); } } } - void resize_reduce_is_data_stay_consistent(int newRowsCount, int newColsCount) { - vec.resize(newRowsCount, newColsCount, 0); - assert_fill_with(vec, newRowsCount, newColsCount); - } + void assert_resize_increase_initializes_new(size_t newRows, size_t newCols) { + vec.resize(newRows, newCols, 0); + ASSERT_EQ(vec.rows(), newRows); + ASSERT_EQ(vec.cols(), newCols); - void resize_increase_is_data_stay_consistent(int newRowsCount, int newColsCount) { - vec.resize(newRowsCount, newColsCount, 0); - assert_fill_with(vec, ROWS_COUNT_INIT, COLS_COUNT_INIT); + // Check old data preserved + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(r, c), r * COLS_COUNT_INIT + c); + } + } - for (int r = 0; r < newRowsCount; ++r) { - for (int c = 0; c < newColsCount; ++c) { - if (r < ROWS_COUNT_INIT || c < COLS_COUNT_INIT) continue; - ASSERT_EQ(vec.element(r, c), 0); + // Check new elements initialized to 0 + for (size_t r = 0; r < newRows; ++r) { + for (size_t c = 0; c < newCols; ++c) { + if (r >= ROWS_COUNT_INIT || c >= COLS_COUNT_INIT) { + EXPECT_EQ(vec.element(r, c), 0); + } } } } }; -TEST_F(Vector2D, resize_is_increase_col_count) { - vec.resize(ROWS_COUNT_INIT, COLS_COUNT_INCREASE, 0); - ASSERT_EQ(vec.cols(), COLS_COUNT_INCREASE); +TEST_F(Vector2DResizeTest, resize_increase_both_preserves_data) { + assert_resize_increase_initializes_new(ROWS_COUNT_INCREASE, COLS_COUNT_INCREASE); } -TEST_F(Vector2D, resize_is_reduce_col_count) { - vec.resize(ROWS_COUNT_INIT, COLS_COUNT_REDUCE, 0); - ASSERT_EQ(vec.cols(), COLS_COUNT_REDUCE); +TEST_F(Vector2DResizeTest, resize_increase_rows_only_preserves_data) { + assert_resize_increase_initializes_new(ROWS_COUNT_INCREASE, COLS_COUNT_INIT); } -TEST_F(Vector2D, resize_is_increase_rows_count) { - vec.resize(ROWS_COUNT_INCREASE, COLS_COUNT_INIT, 0); - ASSERT_EQ(vec.rows(), ROWS_COUNT_INCREASE); +TEST_F(Vector2DResizeTest, resize_increase_cols_only_preserves_data) { + assert_resize_increase_initializes_new(ROWS_COUNT_INIT, COLS_COUNT_INCREASE); } -TEST_F(Vector2D, resize_is_reduce_rows_count) { - vec.resize(ROWS_COUNT_REDUCE, COLS_COUNT_INIT, 0); - ASSERT_EQ(vec.rows(), ROWS_COUNT_REDUCE); +TEST_F(Vector2DResizeTest, resize_reduce_both_preserves_data) { + assert_resize_reduce_preserves_data(ROWS_COUNT_REDUCE, COLS_COUNT_REDUCE); } -TEST_F(Vector2D, resize_increase_both_is_data_stay_consistent) { - resize_increase_is_data_stay_consistent(ROWS_COUNT_INCREASE, COLS_COUNT_INCREASE); +TEST_F(Vector2DResizeTest, resize_reduce_rows_only_preserves_data) { + assert_resize_reduce_preserves_data(ROWS_COUNT_REDUCE, COLS_COUNT_INIT); } -TEST_F(Vector2D, resize_reduce_cols_is_data_stay_consistent) { - resize_reduce_is_data_stay_consistent(ROWS_COUNT_INIT, COLS_COUNT_REDUCE); +TEST_F(Vector2DResizeTest, resize_reduce_cols_only_preserves_data) { + assert_resize_reduce_preserves_data(ROWS_COUNT_INIT, COLS_COUNT_REDUCE); } -TEST_F(Vector2D, resize_reduce_rows_is_data_stay_consistent) { - resize_reduce_is_data_stay_consistent(ROWS_COUNT_REDUCE, COLS_COUNT_INIT); +TEST_F(Vector2DResizeTest, resize_to_zero_creates_empty) { + vec.resize(0, 0, 42); + EXPECT_TRUE(vec.isEmpty()); + EXPECT_EQ(vec.rows(), 0); + EXPECT_EQ(vec.cols(), 0); } -TEST_F(Vector2D, resize_reduce_both_is_data_stay_consistent) { - resize_reduce_is_data_stay_consistent(ROWS_COUNT_REDUCE, COLS_COUNT_REDUCE); +TEST_F(Vector2DResizeTest, resize_same_dimensions_does_nothing) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + PIVector oldData = vec.asPlainVector(); + + vec.resize(oldRows, oldCols, 999); + EXPECT_EQ(vec.rows(), oldRows); + EXPECT_EQ(vec.cols(), oldCols); + EXPECT_EQ(vec.asPlainVector(), oldData); // Data unchanged } + +// ==================== SEARCH AND LOOKUP TESTS ==================== +TEST_F(Vector2DTest, contains_finds_element_in_flat_vector) { + EXPECT_TRUE(vec.contains(5 * COLS_COUNT_INIT + 7)); + EXPECT_FALSE(vec.contains(-999)); + EXPECT_TRUE(vec.contains(0)); // first element + EXPECT_TRUE(vec.contains(ROWS_COUNT_INIT * COLS_COUNT_INIT - 1)); // last element +} + +TEST_F(Vector2DTest, contains_with_start_parameter_works) { + int target = 10 * COLS_COUNT_INIT + 15; + EXPECT_TRUE(vec.contains(target)); + EXPECT_TRUE(vec.contains(target, target)); // start exactly at target (inclusive) + EXPECT_FALSE(vec.contains(target, target + 1)); // start after target +} + +TEST_F(Vector2DTest, contains_vector_of_elements_works) { + PIVector searchFor; + searchFor << 100 << 200 << 300; + EXPECT_TRUE(vec.contains(searchFor)); + + searchFor << -999; + EXPECT_FALSE(vec.contains(searchFor)); +} + +TEST_F(Vector2DTest, entries_counts_occurrences) { + // Add some duplicates + vec.fill(0); + vec.element(5, 5) = 42; + vec.element(10, 10) = 42; + + EXPECT_EQ(vec.entries(42), 2); + EXPECT_EQ(vec.entries(-1), 0); +} + +TEST_F(Vector2DTest, entries_with_predicate_counts_matches) { + auto isEven = [](const int & e) { return e % 2 == 0; }; + int evenCount = 0; + for (size_t i = 0; i < vec.size(); ++i) { + if (vec.asPlainVector()[i] % 2 == 0) evenCount++; + } + EXPECT_EQ(vec.entries(isEven), evenCount); +} + +TEST_F(Vector2DTest, indexOf_finds_first_occurrence) { + int firstTarget = 5 * COLS_COUNT_INIT + 10; + EXPECT_EQ(vec.indexOf(firstTarget), static_cast(firstTarget)); + + // Add duplicate later + vec.asPlainVector()[20 * COLS_COUNT_INIT + 15] = firstTarget; + EXPECT_EQ(vec.indexOf(firstTarget), static_cast(firstTarget)); // Still first +} + +TEST_F(Vector2DTest, indexOf_with_start_works) { + int target = 15 * COLS_COUNT_INIT + 20; + EXPECT_EQ(vec.indexOf(target, target + 1), -1); + EXPECT_EQ(vec.indexOf(target, target), static_cast(target)); +} + +TEST_F(Vector2DTest, indexWhere_finds_first_match) { + auto isLarge = [](const int & e) { return e > 500; }; + ssize_t expected = 501; // First element > 500 + EXPECT_EQ(vec.indexWhere(isLarge), expected); +} + +TEST_F(Vector2DTest, lastIndexOf_finds_last_occurrence) { + // Add duplicate later + int target = 8 * COLS_COUNT_INIT + 8; + vec.asPlainVector()[25 * COLS_COUNT_INIT + 25] = target; + + EXPECT_EQ(vec.lastIndexOf(target), static_cast(25 * COLS_COUNT_INIT + 25)); +} + +TEST_F(Vector2DTest, lastIndexWhere_finds_last_match) { + auto isLarge = [](const int & e) { return e > 900; }; + ssize_t expected = vec.size() - 1; // Last element + EXPECT_EQ(vec.lastIndexWhere(isLarge), expected); +} + +// ==================== STATISTICS AND CONDITIONS TESTS ==================== +TEST_F(Vector2DTest, any_returns_true_if_any_match) { + auto isNegative = [](const int & e) { return e < 0; }; + auto isLarge = [](const int & e) { return e > 1000000; }; + + EXPECT_FALSE(vec.any(isNegative)); + EXPECT_FALSE(vec.any(isLarge)); + + auto isPositive = [](const int & e) { return e >= 0; }; + EXPECT_TRUE(vec.any(isPositive)); +} + +TEST_F(Vector2DTest, every_returns_true_if_all_match) { + auto isNonNegative = [](const int & e) { return e >= 0; }; + const int max = ROWS_COUNT_INIT * COLS_COUNT_INIT; + auto isLessThan = [max](const int & e) { return e < max; }; + + EXPECT_TRUE(vec.every(isNonNegative)); + EXPECT_TRUE(vec.every(isLessThan)); + + auto isEven = [](const int & e) { return e % 2 == 0; }; + EXPECT_FALSE(vec.every(isEven)); +} + +// ==================== FILL AND ASSIGN TESTS ==================== +TEST_F(Vector2DTest, fill_sets_all_elements_to_value) { + vec.fill(42); + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(r, c), 42); + } + } +} + +TEST_F(Vector2DTest, fill_with_function_generates_values) { + vec.fill([](size_t i) { return static_cast(i * 2); }); + for (size_t i = 0; i < vec.size(); ++i) { + EXPECT_EQ(vec.asPlainVector()[i], static_cast(i * 2)); + } +} + +TEST_F(Vector2DTest, assign_is_alias_for_fill) { + vec.assign(99); + for (size_t i = 0; i < vec.size(); ++i) { + EXPECT_EQ(vec.asPlainVector()[i], 99); + } +} + +TEST_F(Vector2DTest, assign_with_size_resets_to_single_row) { + vec.assign(10, 77); + EXPECT_EQ(vec.rows(), 1); + EXPECT_EQ(vec.cols(), 10); + for (size_t c = 0; c < 10; ++c) { + EXPECT_EQ(vec.element(0, c), 77); + } +} + +// ==================== COMPARISON TESTS ==================== +TEST_F(Vector2DTest, equality_operator_works) { + PIVector2D same = vec; + EXPECT_EQ(vec, same); + + PIVector2D differentRows(ROWS_COUNT_INIT + 1, COLS_COUNT_INIT); + EXPECT_NE(vec, differentRows); + + PIVector2D differentCols(ROWS_COUNT_INIT, COLS_COUNT_INIT + 1); + EXPECT_NE(vec, differentCols); + + PIVector2D differentData(ROWS_COUNT_INIT, COLS_COUNT_INIT, 99); + EXPECT_NE(vec, differentData); +} + +// ==================== CONVERSION TESTS ==================== +TEST_F(Vector2DTest, toVectors_converts_correctly) { + auto vectors = vec.toVectors(); + EXPECT_EQ(vectors.size(), ROWS_COUNT_INIT); + + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + EXPECT_EQ(vectors[r].size(), COLS_COUNT_INIT); + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vectors[r][c], vec.element(r, c)); + } + } +} + +TEST_F(Vector2DTest, plainVector_returns_underlying_storage) { + const auto & plain = vec.asPlainVector(); + EXPECT_EQ(plain.size(), vec.size()); + + for (size_t i = 0; i < plain.size(); ++i) { + EXPECT_EQ(plain[i], vec.asPlainVector()[i]); + } +} + +TEST_F(Vector2DTest, toPlainVector_returns_copy) { + auto copy = vec.toPlainVector(); + EXPECT_EQ(copy.size(), vec.size()); + EXPECT_NE(copy.data(), vec.data()); // Different memory + + for (size_t i = 0; i < copy.size(); ++i) { + EXPECT_EQ(copy[i], vec.asPlainVector()[i]); + } +} + +// ==================== SWAP TESTS ==================== +TEST_F(Vector2DTest, swap_exchanges_contents) { + PIVector2D other(5, 5, 42); + + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + PIVector oldData = vec.asPlainVector(); + + vec.swap(other); + + EXPECT_EQ(vec.rows(), 5); + EXPECT_EQ(vec.cols(), 5); + for (size_t i = 0; i < vec.size(); ++i) { + EXPECT_EQ(vec.asPlainVector()[i], 42); + } + + EXPECT_EQ(other.rows(), oldRows); + EXPECT_EQ(other.cols(), oldCols); + EXPECT_EQ(other.asPlainVector(), oldData); +} + +// ==================== CLEAR TESTS ==================== +TEST_F(Vector2DTest, clear_removes_all_elements) { + vec.clear(); + EXPECT_TRUE(vec.isEmpty()); + EXPECT_EQ(vec.rows(), 0); + EXPECT_EQ(vec.cols(), 0); + EXPECT_EQ(vec.size(), 0); +} + +// ==================== TRANSPOSE AND REVERSE TESTS ==================== +TEST_F(Vector2DTest, transposed_returns_correct_dimensions) { + auto transposed = vec.transposed(); + EXPECT_EQ(transposed.rows(), COLS_COUNT_INIT); + EXPECT_EQ(transposed.cols(), ROWS_COUNT_INIT); + + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(transposed.element(c, r), vec.element(r, c)); + } + } +} + +TEST_F(Vector2DTest, reverseRows_reverses_row_order) { + auto original = vec; + vec.reverseRows(); + + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(r, c), original.element(ROWS_COUNT_INIT - 1 - r, c)); + } + } +} + +TEST_F(Vector2DTest, reverseColumns_reverses_column_order_in_each_row) { + auto original = vec; + vec.reverseColumns(); + + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(r, c), original.element(r, COLS_COUNT_INIT - 1 - c)); + } + } +} + +TEST_F(Vector2DTest, reverseRows_and_reverseColumns_compose_correctly) { + auto original = vec; + vec.reverseRows(); + vec.reverseColumns(); + + // This should be equivalent to 180-degree rotation + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(r, c), original.element(ROWS_COUNT_INIT - 1 - r, COLS_COUNT_INIT - 1 - c)); + } + } +} + +// ==================== RANGE TESTS ==================== +TEST_F(Vector2DTest, getRange_returns_submatrix) { + auto sub = vec.getRange(5, 10, 8, 15); + EXPECT_EQ(sub.rows(), 10); + EXPECT_EQ(sub.cols(), 15); + + for (size_t r = 0; r < 10; ++r) { + for (size_t c = 0; c < 15; ++c) { + EXPECT_EQ(sub.element(r, c), vec.element(5 + r, 8 + c)); + } + } +} + +TEST_F(Vector2DTest, getRange_with_invalid_params_returns_empty) { + auto sub1 = vec.getRange(ROWS_COUNT_INIT, 5, 0, 5); + EXPECT_TRUE(sub1.isEmpty()); + + auto sub2 = vec.getRange(0, 5, COLS_COUNT_INIT, 5); + EXPECT_TRUE(sub2.isEmpty()); +} + +TEST_F(Vector2DTest, getRange_truncates_out_of_bounds) { + auto sub = vec.getRange(ROWS_COUNT_INIT - 5, 10, COLS_COUNT_INIT - 5, 10); + EXPECT_EQ(sub.rows(), 5); + EXPECT_EQ(sub.cols(), 5); +} + +// ==================== FUNCTIONAL PROGRAMMING TESTS ==================== +TEST_F(Vector2DTest, map_transforms_elements) { + auto doubled = vec.map([](const int & e) { return e * 2; }); + EXPECT_EQ(doubled.rows(), vec.rows()); + EXPECT_EQ(doubled.cols(), vec.cols()); + + for (size_t r = 0; r < vec.rows(); ++r) { + for (size_t c = 0; c < vec.cols(); ++c) { + EXPECT_EQ(doubled.element(r, c), vec.element(r, c) * 2); + } + } +} + +TEST_F(Vector2DTest, map_changes_type) { + auto asString = vec.map([](const int & e) { return PIString::fromNumber(e); }); + EXPECT_EQ(asString.rows(), vec.rows()); + EXPECT_EQ(asString.cols(), vec.cols()); + + for (size_t r = 0; r < vec.rows(); ++r) { + for (size_t c = 0; c < vec.cols(); ++c) { + EXPECT_EQ(asString.element(r, c), PIString::fromNumber(vec.element(r, c))); + } + } +} + +TEST_F(Vector2DTest, mapIndexed_uses_indices) { + auto indexed = vec.mapIndexed([](size_t r, size_t c, const int & e) { return static_cast(r * 1000 + c); }); + + for (size_t r = 0; r < vec.rows(); ++r) { + for (size_t c = 0; c < vec.cols(); ++c) { + EXPECT_EQ(indexed.element(r, c), static_cast(r * 1000 + c)); + } + } +} + +TEST_F(Vector2DTest, forEach_readonly_visits_all_elements) { + size_t count = 0; + vec.asPlainVector().forEach([&count](const int &) { count++; }); + EXPECT_EQ(count, vec.size()); +} + +TEST_F(Vector2DTest, forEach_modifying_changes_elements) { + vec.asPlainVector().forEach([](int & e) { e++; }); + + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(r, c), r * COLS_COUNT_INIT + c + 1); + } + } +} + +/* +TEST_F(Vector2DTest, forEachIndexed_readonly_uses_indices) { + bool allCorrect = true; + vec.forEachIndexed([&allCorrect](size_t r, size_t c, const int & e) { + if (e != static_cast(r * COLS_COUNT_INIT + c)) allCorrect = false; + }); + EXPECT_TRUE(allCorrect); +} + +TEST_F(Vector2DTest, forEachIndexed_modifying_uses_indices) { + vec.forEachIndexed([](size_t r, size_t c, int & e) { e = static_cast(r * 1000 + c); }); + + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + EXPECT_EQ(vec.element(r, c), static_cast(r * 1000 + c)); + } + } +} +*/ + +TEST_F(Vector2DTest, reduce_accumulates_correctly) { + int sum = vec.reduce([](const int & e, const int & acc) { return e + acc; }); + + int expected = (vec.size() - 1) * vec.size() / 2; + EXPECT_EQ(sum, expected); +} + +TEST_F(Vector2DTest, reduce_with_initial_value) { + int sum = vec.reduce([](const int & e, const int & acc) { return e + acc; }, 100); + + int expected = (vec.size() - 1) * vec.size() / 2 + 100; + EXPECT_EQ(sum, expected); +} + +TEST_F(Vector2DTest, reduceIndexed_uses_indices) { + int sum = + vec.reduceIndexed([](size_t r, size_t c, const int & e, const int & acc) { return acc + static_cast(r * 1000 + c); }); + + int expected = 0; + for (size_t r = 0; r < ROWS_COUNT_INIT; ++r) { + for (size_t c = 0; c < COLS_COUNT_INIT; ++c) { + expected += r * 1000 + c; + } + } + EXPECT_EQ(sum, expected); +} + +// ==================== REMOVAL TESTS ==================== +TEST_F(Vector2DTest, removeRow_removes_specified_row) { + size_t oldRows = vec.rows(); + auto rowContent = vec[10].toVector(); + + vec.removeRow(10); + EXPECT_EQ(vec.rows(), oldRows - 1); + + // Check rows after 10 shifted up + for (size_t r = 10; r < vec.rows(); ++r) { + for (size_t c = 0; c < vec.cols(); ++c) { + EXPECT_EQ(vec.element(r, c), (r + 1) * COLS_COUNT_INIT + c); + } + } +} + +TEST_F(Vector2DTest, removeRow_invalid_index_does_nothing) { + size_t oldRows = vec.rows(); + vec.removeRow(ROWS_COUNT_INIT + 10); + EXPECT_EQ(vec.rows(), oldRows); +} + +TEST_F(Vector2DTest, removeRow_last_row_works) { + size_t oldRows = vec.rows(); + vec.removeRow(oldRows - 1); + EXPECT_EQ(vec.rows(), oldRows - 1); +} + +TEST_F(Vector2DTest, removeColumn_removes_specified_column) { + size_t oldCols = vec.cols(); + vec.removeColumn(15); + EXPECT_EQ(vec.cols(), oldCols - 1); + + for (size_t r = 0; r < vec.rows(); ++r) { + for (size_t c = 0; c < 15; ++c) { + EXPECT_EQ(vec.element(r, c), r * COLS_COUNT_INIT + c); + } + for (size_t c = 15; c < vec.cols(); ++c) { + EXPECT_EQ(vec.element(r, c), r * COLS_COUNT_INIT + c + 1); + } + } +} + +TEST_F(Vector2DTest, removeColumn_invalid_index_does_nothing) { + size_t oldCols = vec.cols(); + vec.removeColumn(COLS_COUNT_INIT + 10); + EXPECT_EQ(vec.cols(), oldCols); +} + +TEST_F(Vector2DTest, removeColumn_last_column_works) { + size_t oldCols = vec.cols(); + vec.removeColumn(oldCols - 1); + EXPECT_EQ(vec.cols(), oldCols - 1); +} + +TEST_F(Vector2DTest, removeRowsWhere_removes_matching_rows) { + auto isSpecial = [](const PIVector2D::RowConst & row) { + return row[0] == 999; // First element is 999 + }; + + const size_t rowsCont = 5; + // Add some identifiable rows + for (size_t r = 0; r < rowsCont; ++r) { + vec.addRow(PIVector(COLS_COUNT_INIT, 999)); + } + EXPECT_EQ(vec.filterRows(isSpecial).rows(), rowsCont); + + vec.removeRowsWhere(isSpecial); + EXPECT_EQ(vec.rows(), ROWS_COUNT_INIT); + + // Verify no rows with 999 remain + auto res = vec.filterRows(isSpecial); + EXPECT_TRUE(res.isEmpty()); +} + +TEST_F(Vector2DTest, removeColumnsWhere_removes_matching_columns) { + // Make some columns have a distinctive first element + for (size_t c = 0; c < 5; ++c) { + vec.element(0, c) = 777; + } + + auto isSpecial = [](const PIVector2D::ColConst & col) { + return col[0] == 777; // First element is 777 + }; + + size_t oldCols = vec.cols(); + vec.removeColumnsWhere(isSpecial); + EXPECT_EQ(vec.cols(), oldCols - 5); + + // Verify no columns with 777 in first row remain + for (size_t c = 0; c < vec.cols(); ++c) { + EXPECT_NE(vec.element(0, c), 777); + } +} + +// ==================== FILTER TESTS ==================== +TEST_F(Vector2DTest, filterRows_returns_only_matching_rows) { + auto rowsWithEvenFirst = vec.filterRows([](const PIVector2D::RowConst & row) { return row[0] % 2 == 0; }); + + // First element of row r is r * COLS_COUNT_INIT + // This is even for all rows since COLS_COUNT_INIT is even (34) + EXPECT_EQ(rowsWithEvenFirst.rows(), ROWS_COUNT_INIT); + + auto rowsWithLargeFirst = vec.filterRows([](const PIVector2D::RowConst & row) { return row[0] > 500; }); + + // First element > 500 means r * 34 > 500 -> r > 14.7 + EXPECT_EQ(rowsWithLargeFirst.rows(), ROWS_COUNT_INIT - 15); +} + +TEST_F(Vector2DTest, filterColumns_returns_only_matching_columns) { + auto colsWithEvenFirst = vec.filterColumns([](const PIVector2D::ColConst & col) { return col[0] % 2 == 0; }); + + // First element of column c is c + EXPECT_EQ(colsWithEvenFirst.cols(), COLS_COUNT_INIT / 2); +} + +TEST_F(Vector2DTest, filterColumns_empty_result_returns_empty) { + auto noCols = vec.filterColumns([](const PIVector2D::ColConst &) { return false; }); + EXPECT_TRUE(noCols.isEmpty()); +} + +// ==================== EDGE CASE TESTS ==================== +TEST(Vector2DEdgeTest, empty_vector_operations) { + PIVector2D empty; + + EXPECT_TRUE(empty.isEmpty()); + EXPECT_EQ(empty.rows(), 0); + EXPECT_EQ(empty.cols(), 0); + + // These should not crash + empty.clear(); + empty.fill(42); + empty.transposed(); + empty.reverseRows(); + empty.reverseColumns(); + + auto range = empty.getRange(0, 5, 0, 5); + EXPECT_TRUE(range.isEmpty()); + + auto filtered = empty.filterRows([](const PIVector2D::RowConst &) { return true; }); + EXPECT_TRUE(filtered.isEmpty()); +} + +TEST(Vector2DEdgeTest, single_element_vector) { + PIVector2D single(1, 1, 42); + + EXPECT_EQ(single.rows(), 1); + EXPECT_EQ(single.cols(), 1); + EXPECT_EQ(single.element(0, 0), 42); + + auto row = single[0]; + EXPECT_EQ(row.size(), 1); + EXPECT_EQ(row[0], 42); + + auto col = single.col(0); + EXPECT_EQ(col.size(), 1); + EXPECT_EQ(col[0], 42); + + single.reverseRows(); // Should do nothing + EXPECT_EQ(single.element(0, 0), 42); + + single.reverseColumns(); // Should do nothing + EXPECT_EQ(single.element(0, 0), 42); +} + +// ==================== OUTPUT TESTS ==================== +TEST_F(Vector2DTest, picout_operator_works) { + // Just test that it compiles and doesn't crash + PICout s; + s << vec; + // No assertion, just ensure it runs +} + +#ifdef PIP_STD_IOSTREAM +TEST_F(Vector2DTest, iostream_operator_works) { + // PIVector2D doesn't have direct iostream operator, + // but PIVector does, and we can test conversion + std::stringstream ss; + ss << vec.plainVector(); + // No assertion, just ensure it runs +} +#endif