Vibecoding PIVector2D - add funcs and doc
Добавь в файл pivector2d.h комментарии для Doxygen ко всем классам и всем функциям. Комментарии должны быть в таком же стиле как в файле pivector.h. Проанализируй функциональность классов pivector2d в файле pivector2d.h и класс pivector в файле pivector.h и добавь недостающую функциональность в pivector2d по аналогии с pivector.
This commit is contained in:
@@ -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<T>: 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<T> and PIVector<PIVector<T> >
|
||||
*/
|
||||
//! \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<typename T>
|
||||
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<T> & 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<T> && 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<PIVector<T>> & 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<T>;
|
||||
|
||||
@@ -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<size_t>(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<T> & other) {
|
||||
const size_t sz = piMin<size_t>(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<T> toVector() const { return PIVector<T>(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<T>;
|
||||
|
||||
private:
|
||||
inline Col(PIVector2D<T> * p, size_t row): p_(&(p->mat)) {
|
||||
inline Col(PIVector2D<T> * p, size_t col): p_(&(p->mat)) {
|
||||
step_ = p->cols_;
|
||||
row_ = row;
|
||||
col_ = col;
|
||||
sz_ = p->rows_;
|
||||
}
|
||||
PIVector<T> * 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<size_t>(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<T> & other) {
|
||||
|
||||
//! \~english Assigns the contents of a \a PIVector to this column.
|
||||
//! \~russian Присваивает этому столбцу содержимое \a PIVector.
|
||||
inline Col & operator=(const PIVector<T> & other) {
|
||||
const size_t sz = piMin<size_t>(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<T> toVector() const {
|
||||
PIVector<T> 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<T>;
|
||||
|
||||
@@ -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<T> toVector() const { return PIVector<T>(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<T>;
|
||||
|
||||
private:
|
||||
inline ColConst(const PIVector2D<T> * p, size_t row): p_(&(p->mat)) {
|
||||
inline ColConst(const PIVector2D<T> * p, size_t col): p_(&(p->mat)) {
|
||||
step_ = p->cols_;
|
||||
row_ = row;
|
||||
col_ = col;
|
||||
sz_ = p->rows_;
|
||||
}
|
||||
const PIVector<T> * 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<T> toVector() const {
|
||||
PIVector<T> 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<T> & setRow(size_t row, const Row & other) {
|
||||
const size_t sz = piMin<size_t>(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<T> & setRow(size_t row, const RowConst & other) {
|
||||
const size_t sz = piMin<size_t>(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<T> & setRow(size_t row, const PIVector<T> & other) {
|
||||
const size_t sz = piMin<size_t>(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<T> & addRow(const Row & other) {
|
||||
if (cols_ == 0) cols_ = other.sz_;
|
||||
const size_t sz = piMin<size_t>(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<T> & addRow(const RowConst & other) {
|
||||
if (cols_ == 0) cols_ = other.sz_;
|
||||
const size_t sz = piMin<size_t>(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<T> & addRow(const PIVector<T> & other) {
|
||||
if (cols_ == 0) cols_ = other.size();
|
||||
const size_t sz = piMin<size_t>(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<T> & 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<T> 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> & 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> & t) const { return !(*this == t); }
|
||||
|
||||
//! \~english Converts the 2D array to a vector of vectors (PIVector<PIVector<T>>).
|
||||
//! \~russian Преобразует двумерный массив в вектор векторов (PIVector<PIVector<T>>).
|
||||
inline PIVector<PIVector<T>> toVectors() const {
|
||||
PIVector<PIVector<T>> 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<T> & asPlainVector() const { return mat; }
|
||||
|
||||
//! \~english Returns a reference to the underlying flat \a PIVector.
|
||||
//! \~russian Возвращает ссылку на внутренний плоский \a PIVector.
|
||||
inline PIVector<T> & asPlainVector() { return mat; }
|
||||
|
||||
//! \~english Returns a copy of the underlying flat \a PIVector.
|
||||
//! \~russian Возвращает копию внутреннего плоского \a PIVector.
|
||||
inline PIVector<T> toPlainVector() const { return mat; }
|
||||
|
||||
inline PIVector<T> & plainVector() { return mat; }
|
||||
|
||||
inline const PIVector<T> & plainVector() const { return mat; }
|
||||
|
||||
//! \~english Swaps this 2D array with another.
|
||||
//! \~russian Меняет местами этот двумерный массив с другим.
|
||||
inline void swap(PIVector2D<T> & other) {
|
||||
mat.swap(other.mat);
|
||||
piSwap<size_t>(rows_, other.rows_);
|
||||
piSwap<size_t>(cols_, other.cols_);
|
||||
}
|
||||
|
||||
//! \internal
|
||||
template<typename T1 = T, typename std::enable_if<std::is_trivially_copyable<T1>::value, int>::type = 0>
|
||||
inline PIVector2D<T> & _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<typename ST>
|
||||
inline PIVector2D<ST> map(std::function<ST(const T & e)> f) const {
|
||||
return PIVector2D<ST>(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<T> & 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<bool(const T & e)> 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<ssize_t row, ssize_t col>
|
||||
//! \~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<bool(const T & e)> 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<bool(const T & e)> 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<bool(const T & e)> 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<bool(const T & e)> test) const { return mat.every(test); }
|
||||
|
||||
//! \~english Fills the entire 2D array with copies of `e`.
|
||||
//! \~russian Заполняет весь двумерный массив копиями `e`.
|
||||
inline PIVector2D<T> & 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<T> & fill(std::function<T(size_t i)> f) {
|
||||
mat.fill(f);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//! \~english Same as \a fill().
|
||||
//! \~russian То же, что и \a fill().
|
||||
inline PIVector2D<T> & 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<T> & 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<T> transposed() const {
|
||||
if (isEmpty()) return PIVector2D<T>();
|
||||
PIVector2D<T> 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<T> & 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<T> temp = r1.toVector();
|
||||
r1 = r2.toVector();
|
||||
r2 = temp;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
//! \~english Reverses the order of columns in each row in place.
|
||||
//! \~russian Изменяет порядок столбцов в каждой строке на обратный на месте.
|
||||
inline PIVector2D<T> & 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<T>(currentRow[c], currentRow[cols_ - 1 - c]);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
//! \~english Returns a sub-2D array (a range of rows and columns).
|
||||
//! \~russian Возвращает подмассив (диапазон строк и столбцов).
|
||||
inline PIVector2D<T> 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<T>();
|
||||
size_t actualRowCount = piMin(rowCount, rows_ - rowStart);
|
||||
size_t actualColCount = piMin(colCount, cols_ - colStart);
|
||||
|
||||
PIVector2D<T> 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<typename ST>
|
||||
inline PIVector2D<ST> map(std::function<ST(const T & e)> f) const {
|
||||
return PIVector2D<ST>(rows_, cols_, mat.template map<ST>(f));
|
||||
}
|
||||
|
||||
//! \~english Applies a function (with row and col indices) to each element and returns a new 2D array.
|
||||
//! \~russian Применяет функцию (с индексами строки и столбца) к каждому элементу и возвращает новый двумерный массив.
|
||||
template<typename ST>
|
||||
inline PIVector2D<ST> mapIndexed(std::function<ST(size_t row, size_t col, const T & e)> f) const {
|
||||
PIVector<ST> 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<ST>(rows_, cols_, std::move(mappedMat));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
//! \~english Executes a read-only function for each element.
|
||||
//! \~russian Выполняет функцию только для чтения для каждого элемента.
|
||||
inline void forEach(std::function<void(const T &)> f) const { mat.forEach(f); }
|
||||
|
||||
//! \~english Executes a function for each element, allowing modification.
|
||||
//! \~russian Выполняет функцию для каждого элемента, позволяя их изменять.
|
||||
inline PIVector2D<T> & forEach(std::function<void(T &)> 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<void(size_t row, size_t col, const T &)> 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<T> & forEachIndexed(std::function<void(size_t row, size_t col, T &)> 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<typename ST>
|
||||
inline ST reduce(std::function<ST(const T & e, const ST & acc)> f, const ST & initial = ST()) const {
|
||||
return mat.template reduce<ST>(f, initial);
|
||||
}
|
||||
|
||||
//! \~english Accumulates a value across all elements with indices.
|
||||
//! \~russian Аккумулирует значение по всем элементам с индексами.
|
||||
template<typename ST>
|
||||
inline ST reduceIndexed(std::function<ST(size_t row, size_t col, const T & e, const ST & acc)> 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<T> & 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<T> & removeColumn(size_t col) {
|
||||
if (col >= cols_ || rows_ == 0) return *this;
|
||||
PIVector2D<T> 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<T> & removeRowsWhere(std::function<bool(const RowConst &)> 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<T> & removeColumnsWhere(std::function<bool(const ColConst &)> 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<T> filterRows(std::function<bool(const RowConst &)> test) const {
|
||||
PIVector2D<T> 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<T> filterColumns(std::function<bool(const ColConst &)> test) const {
|
||||
if (isEmpty()) return PIVector2D<T>();
|
||||
PIVector<size_t> goodCols;
|
||||
for (size_t c = 0; c < cols_; ++c) {
|
||||
if (test(col(c))) {
|
||||
goodCols << c;
|
||||
}
|
||||
}
|
||||
PIVector2D<T> 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<T> filterElements(std::function<bool(const T &)> test) const {
|
||||
PIVector<T> filtered = mat.filter(test);
|
||||
if (filtered.isEmpty()) return PIVector2D<T>();
|
||||
return PIVector2D<T>(1, filtered.size(), filtered);
|
||||
}
|
||||
|
||||
protected:
|
||||
size_t rows_, cols_;
|
||||
PIVector<T> mat;
|
||||
};
|
||||
|
||||
|
||||
//! \relatesalso PICout
|
||||
//! \~english Output operator for \a PIVector2D to \a PICout.
|
||||
//! \~russian Оператор вывода \a PIVector2D в \a PICout.
|
||||
template<typename T>
|
||||
inline PICout operator<<(PICout s, const PIVector2D<T> & v) {
|
||||
s.saveAndSetControls(0);
|
||||
@@ -346,5 +863,6 @@ inline PICout operator<<(PICout s, const PIVector2D<T> & v) {
|
||||
return s;
|
||||
}
|
||||
|
||||
//! \}
|
||||
|
||||
#endif // PIVECTOR2D_H
|
||||
|
||||
@@ -1,85 +1,937 @@
|
||||
#include "pistring.h"
|
||||
#include "pivector2d.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <numeric>
|
||||
|
||||
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<int> 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<int> & 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<int> & 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<int> vec = PIVector2D<int>(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<int> 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<int> 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<int> plain(20);
|
||||
std::iota(plain.data(), plain.data() + 20, 0);
|
||||
|
||||
PIVector2D<int> 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<int>(r * 5 + c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(Vector2DTest, fromPlainVector_move_constructor_reshapesCorrectly) {
|
||||
PIVector<int> plain(20);
|
||||
std::iota(plain.data(), plain.data() + 20, 0);
|
||||
|
||||
PIVector2D<int> 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<int>(r * 5 + c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(Vector2DTest, fromVectorOfVectors_constructor_reshapesCorrectly) {
|
||||
PIVector<PIVector<int>> vectors;
|
||||
vectors << PIVector<int>({1, 2, 3}) << PIVector<int>({4, 5, 6}) << PIVector<int>({7, 8, 9});
|
||||
|
||||
PIVector2D<int> 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<ssize_t>(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<int> 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<int> 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<int> 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<int> 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<int> 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<int>(100 + c));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(Vector2DTest, setRow_with_shorter_vector_truncates) {
|
||||
PIVector<int> 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<int> empty;
|
||||
PIVector<int> 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<int> 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<int> 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<int> 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<int> 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<ssize_t>(firstTarget));
|
||||
|
||||
// Add duplicate later
|
||||
vec.asPlainVector()[20 * COLS_COUNT_INIT + 15] = firstTarget;
|
||||
EXPECT_EQ(vec.indexOf(firstTarget), static_cast<ssize_t>(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<ssize_t>(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<ssize_t>(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<int>(i * 2); });
|
||||
for (size_t i = 0; i < vec.size(); ++i) {
|
||||
EXPECT_EQ(vec.asPlainVector()[i], static_cast<int>(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<int> same = vec;
|
||||
EXPECT_EQ(vec, same);
|
||||
|
||||
PIVector2D<int> differentRows(ROWS_COUNT_INIT + 1, COLS_COUNT_INIT);
|
||||
EXPECT_NE(vec, differentRows);
|
||||
|
||||
PIVector2D<int> differentCols(ROWS_COUNT_INIT, COLS_COUNT_INIT + 1);
|
||||
EXPECT_NE(vec, differentCols);
|
||||
|
||||
PIVector2D<int> 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<int> other(5, 5, 42);
|
||||
|
||||
size_t oldRows = vec.rows();
|
||||
size_t oldCols = vec.cols();
|
||||
PIVector<int> 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<int>([](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<PIString>([](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<int>([](size_t r, size_t c, const int & e) { return static_cast<int>(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<int>(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<int>(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<int>(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<int>(r * 1000 + c));
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
TEST_F(Vector2DTest, reduce_accumulates_correctly) {
|
||||
int sum = vec.reduce<int>([](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<int>([](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<int>([](size_t r, size_t c, const int & e, const int & acc) { return acc + static_cast<int>(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<int>::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<int>(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<int>::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<int>::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<int>::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<int>::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<int>::ColConst &) { return false; });
|
||||
EXPECT_TRUE(noCols.isEmpty());
|
||||
}
|
||||
|
||||
// ==================== EDGE CASE TESTS ====================
|
||||
TEST(Vector2DEdgeTest, empty_vector_operations) {
|
||||
PIVector2D<int> 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<int>::RowConst &) { return true; });
|
||||
EXPECT_TRUE(filtered.isEmpty());
|
||||
}
|
||||
|
||||
TEST(Vector2DEdgeTest, single_element_vector) {
|
||||
PIVector2D<int> 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
|
||||
|
||||
Reference in New Issue
Block a user