From 0b7d565b93ef7a524fc541c41b7eeb8920824b5f Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Fri, 27 Feb 2026 18:40:09 +0300 Subject: [PATCH] pivector2d fix add\remove rows\cols and resize --- libs/main/containers/pivector2d.h | 166 +++++++----------- tests/math/testpivector2d.cpp | 279 +++++++++++++++++++++++++++++- 2 files changed, 342 insertions(+), 103 deletions(-) diff --git a/libs/main/containers/pivector2d.h b/libs/main/containers/pivector2d.h index dd21f4c5..e8191aa4 100644 --- a/libs/main/containers/pivector2d.h +++ b/libs/main/containers/pivector2d.h @@ -777,66 +777,57 @@ public: //! \~russian Добавляет новую строку в конец массива из \a PIVector. inline PIVector2D & addRow(const PIVector & other) { if (cols_ == 0) cols_ = other.size(); - const size_t sz = piMin(cols_, other.size()); - const size_t ps = mat.size(); - mat.resize(mat.size() + cols_); - mat._copyRaw(mat.data(ps), other.data(), sz); + mat.append(other); rows_++; + mat.resize(rows_ * cols_); return *this; } - // TODO: fix - memove existing elements - inline PIVector2D & addEmptyRows(size_t count, const T & f = T()) { - if (count == 0 || cols_ == 0) return *this; + inline PIVector2D & appendRows(size_t count, const T & f = T()) { + if (count == 0) return *this; + if (cols_ == 0) ++cols_; mat.resize(mat.size() + count * cols_, f); rows_ += count; return *this; } - // TODO: fix - memove existing elements - inline PIVector2D & addEmptyColumns(size_t count, const T & f = T()) { - if (count == 0 || rows_ == 0) return *this; - const size_t newCols = cols_ + count; - const size_t newSize = rows_ * newCols; - mat.resize(newSize); - for (size_t r = rows_ - 1; r < rows_; --r) { - for (size_t c = newCols - 1; c >= cols_; --c) { - element(r, c) = f; - } - for (size_t c = cols_; c > 0; --c) { - element(r, c - 1 + count) = element(r, c - 1); - } + inline PIVector2D & appendColumns(size_t count, const T & f = T()) { + if (count == 0) return *this; + if (rows_ == 0) { + mat.resize(count, f); + rows_ = 1; + cols_ = count; + return *this; } + + const size_t newCols = cols_ + count; + mat.reserve(rows_ * newCols); + for (size_t r = rows_; r > 0; --r) { + mat.insert(r * cols_, f, count); + } + cols_ = newCols; return *this; } - inline PIVector2D & deleteRows(size_t row, size_t count) { - if (row >= rows_) return *this; - mat.remove(row * cols_, cols_ * count); - rows_ -= count; - if (mat.isEmpty()) { + inline PIVector2D & deleteRows(size_t row_start, size_t count) { + if (row_start >= rows_ || count == 0) return *this; + mat.remove(row_start * cols_, cols_ * count); + if (isEmpty()) { cols_ = 0; rows_ = 0; + } else { + rows_ -= count; } return *this; } - inline PIVector2D & deleteColumns(size_t col, size_t count) { - if (col >= cols_ || rows_ == 0 || count == 0) return *this; - count = piMin(count, cols_ - col); + inline PIVector2D & deleteColumns(size_t col_start, size_t count) { + if (col_start >= cols_ || rows_ == 0) return *this; + count = piMin(count, cols_ - col_start); + if (count == 0) return *this; for (size_t r = 0; r < rows_; ++r) { - T * dst = mat.data(r * (cols_ - count)); - T * src = mat.data(r * cols_); - if (col > 0) { - memmove(dst, src, col * sizeof(T)); - dst += col; - src += col; - } - size_t remaining = (cols_ - count) - col; - if (remaining > 0) { - memmove(dst, src + 1, remaining * sizeof(T)); - } + mat.remove(r * (cols_ - count) + col_start, count); } cols_ -= count; mat.resize(rows_ * cols_); @@ -847,32 +838,25 @@ public: //! \~russian Добавляет новую строку в конец массива из \a ColConst. inline PIVector2D & addColumn(const ColConst & other) { if (other.size() == 0) return *this; - if (size() == 0) { - _resizeRaw(other.size(), 1); + if (isEmpty()) { + mat.reserve(other.size()); for (size_t r = 0; r < other.size(); ++r) { - element(r, 0) = other[r]; + mat.append(other[r]); } + rows_ = mat.size(); + cols_ = 1; return *this; } - const size_t oldCols = cols_; - const size_t newCols = oldCols + 1; - const size_t newSize = rows_ * newCols; - - mat._resizeRaw(newSize); - - for (size_t r = rows_ - 1; r > 0; --r) { - T * src = mat.data(r * oldCols); - T * dst = mat.data(r * newCols); - memmove(dst, src, oldCols * sizeof(T)); - if (r < other.size()) { - mat._copyRaw(&(dst[oldCols]), &(other[r]), 1); + const size_t newCols = cols_ + 1; + mat.reserve(rows_ * newCols); + for (size_t r = rows_; r > 0; --r) { + if (r - 1 < other.size()) { + mat.insert(r * cols_, other[r - 1]); } else { - const T tmp = T(); - mat._copyRaw(&(dst[oldCols]), &tmp, 1); + mat.insert(r * cols_); } } - mat._copyRaw(mat.data(oldCols), &(other[0]), 1); cols_ = newCols; return *this; @@ -882,32 +866,22 @@ public: //! \~russian Добавляет новую строку в конец массива из \a PIVector. inline PIVector2D & addColumn(const PIVector & other) { if (other.size() == 0) return *this; - if (size() == 0) { - _resizeRaw(other.size(), 1); - for (size_t r = 0; r < other.size(); ++r) { - element(r, 0) = other[r]; - } + if (isEmpty()) { + mat.append(other); + rows_ = mat.size(); + cols_ = 1; return *this; } - const size_t oldCols = cols_; - const size_t newCols = oldCols + 1; - const size_t newSize = rows_ * newCols; - - mat._resizeRaw(newSize); - - for (size_t r = rows_ - 1; r > 0; --r) { - T * src = mat.data(r * oldCols); - T * dst = mat.data(r * newCols); - memmove(dst, src, oldCols * sizeof(T)); - if (r < other.size()) { - mat._copyRaw(&(dst[oldCols]), &(other[r]), 1); + const size_t newCols = cols_ + 1; + mat.reserve(rows_ * newCols); + for (size_t r = rows_; r > 0; --r) { + if (r - 1 < other.size()) { + mat.insert(r * cols_, other[r - 1]); } else { - const T tmp = T(); - mat._copyRaw(&(dst[oldCols]), &tmp, 1); + mat.insert(r * cols_); } } - mat._copyRaw(mat.data(oldCols), &(other[0]), 1); cols_ = newCols; return *this; @@ -938,7 +912,19 @@ public: rows_ = rows; return *this; } - return __resize(rows, cols, f); + if (cols > cols_) { + appendColumns(cols - cols_, f); + } + if (rows > rows_) { + appendRows(rows - rows_, f); + } + if (cols < cols_) { + deleteColumns(cols, cols_ - cols); + } + if (rows < rows_) { + deleteRows(rows, rows_ - rows); + } + return *this; } //! \~english Equality operator. @@ -1261,9 +1247,7 @@ public: //! \~english If the last row is removed and the array becomes empty, \a cols() is set to 0. //! \~russian Если удаляется последняя строка и массив становится пустым, \a cols() устанавливается в 0. //! \sa removeColumn(), PIVector::remove() - inline PIVector2D & removeRow(size_t row) { - return deleteRows(row, 1); - } + inline PIVector2D & removeRow(size_t row) { return deleteRows(row, 1); } //! \~english Removes a column from the 2D array. //! \~russian Удаляет столбец из двумерного массива. @@ -1271,9 +1255,7 @@ public: //! \~english This operation is more expensive than removing a row because elements must be moved. //! \~russian Эта операция дороже, чем удаление строки, поскольку требуется перемещение элементов. //! \sa removeRow(), PIVector::remove() - inline PIVector2D & removeColumn(size_t col) { - return deleteColumns(col, 1); - } + inline PIVector2D & removeColumn(size_t col) { return deleteColumns(col, 1); } //! \~english Removes all rows that satisfy a condition. //! \~russian Удаляет все строки, удовлетворяющие условию. @@ -1343,22 +1325,6 @@ public: } protected: - inline PIVector2D & __resize(size_t rows, size_t cols, const T & f) { - if (cols > cols_) { - addEmptyColumns(cols - cols_, f); - } - if (rows > rows_) { - addEmptyRows(rows - rows_, f); - } - if (cols < cols_) { - deleteColumns(cols, cols_ - cols); - } - if (rows < rows_) { - deleteRows(rows, rows_ - rows); - } - return *this; - } - size_t rows_, cols_; PIVector mat; }; diff --git a/tests/math/testpivector2d.cpp b/tests/math/testpivector2d.cpp index 0b47aac5..a744becc 100644 --- a/tests/math/testpivector2d.cpp +++ b/tests/math/testpivector2d.cpp @@ -379,6 +379,280 @@ TEST_F(Vector2DTest, addRow_with_shorter_vector_uses_min) { } } +// ==================== APPEND ROWS TESTS ==================== + +TEST_F(Vector2DTest, appendRows_adds_rows_at_bottom) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + + vec.appendRows(5, 42); + + EXPECT_EQ(vec.rows(), oldRows + 5); + EXPECT_EQ(vec.cols(), oldCols); + + // Original data preserved + for (size_t r = 0; r < oldRows; ++r) { + for (size_t c = 0; c < oldCols; ++c) { + EXPECT_EQ(vec.element(r, c), static_cast(r * COLS_COUNT_INIT + c)); + } + } + + // New rows filled with 42 + for (size_t r = oldRows; r < vec.rows(); ++r) { + for (size_t c = 0; c < oldCols; ++c) { + EXPECT_EQ(vec.element(r, c), 42); + } + } +} + +TEST_F(Vector2DTest, appendRows_with_zero_count_does_nothing) { + auto oldVec = vec; + vec.appendRows(0); + EXPECT_EQ(vec, oldVec); +} + +TEST_F(Vector2DTest, appendRows_on_empty_matrix) { + PIVector2D empty; + empty.appendRows(5, 99); + EXPECT_TRUE(empty.isNotEmpty()); + EXPECT_EQ(empty.rows(), 5); + EXPECT_EQ(empty.cols(), 1); + EXPECT_EQ(empty.size(), empty.entries(99)); +} + +TEST_F(Vector2DTest, appendRows_with_default_value) { + size_t oldRows = vec.rows(); + + vec.appendRows(3); + + for (size_t r = oldRows; r < vec.rows(); ++r) { + for (size_t c = 0; c < vec.cols(); ++c) { + EXPECT_EQ(vec.element(r, c), 0); + } + } +} + +// ==================== APPEND COLUMNS TESTS ==================== + +TEST_F(Vector2DTest, appendColumns_adds_columns_at_right) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + + vec.appendColumns(3, 99); + + EXPECT_EQ(vec.rows(), oldRows); + EXPECT_EQ(vec.cols(), oldCols + 3); + + // Original data preserved in original columns + for (size_t r = 0; r < oldRows; ++r) { + for (size_t c = 0; c < oldCols; ++c) { + EXPECT_EQ(vec.element(r, c), static_cast(r * COLS_COUNT_INIT + c)); + } + } + + // New columns filled with 99 + for (size_t r = 0; r < oldRows; ++r) { + for (size_t c = oldCols; c < vec.cols(); ++c) { + EXPECT_EQ(vec.element(r, c), 99); + } + } +} + +TEST_F(Vector2DTest, appendColumns_with_zero_count_does_nothing) { + auto oldVec = vec; + vec.appendColumns(0); + EXPECT_EQ(vec, oldVec); +} + +TEST_F(Vector2DTest, appendColumns_on_empty_matrix) { + PIVector2D empty; + empty.appendColumns(5, 99); + EXPECT_TRUE(empty.isNotEmpty()); + EXPECT_EQ(empty.cols(), 5); + EXPECT_EQ(empty.rows(), 1); + EXPECT_EQ(empty.size(), empty.entries(99)); +} + +TEST_F(Vector2DTest, appendColumns_single_column) { + auto oldVec = vec; + + vec.appendColumns(1, 77); + + EXPECT_EQ(vec.rows(), oldVec.rows()); + EXPECT_EQ(vec.cols(), oldVec.cols() + 1); + + // Check original data + for (size_t r = 0; r < vec.rows(); ++r) { + for (size_t c = 0; c < oldVec.cols(); ++c) { + EXPECT_EQ(vec.element(r, c), oldVec.element(r, c)); + } + } + + // Check new column + for (size_t r = 0; r < vec.rows(); ++r) { + EXPECT_EQ(vec.element(r, oldVec.cols()), 77); + } +} + +// ==================== DELETE ROWS TESTS ==================== + +TEST_F(Vector2DTest, deleteRows_removes_rows_from_middle) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + + vec.deleteRows(5, 3); + + EXPECT_EQ(vec.rows(), oldRows - 3); + EXPECT_EQ(vec.cols(), oldCols); + + // Rows before deleted remain + for (size_t r = 0; r < 5; ++r) { + for (size_t c = 0; c < oldCols; ++c) { + EXPECT_EQ(vec.element(r, c), static_cast(r * COLS_COUNT_INIT + c)); + } + } + + // Rows after deleted shifted up + for (size_t r = 5; r < vec.rows(); ++r) { + for (size_t c = 0; c < oldCols; ++c) { + EXPECT_EQ(vec.element(r, c), static_cast((r + 3) * COLS_COUNT_INIT + c)); + } + } +} + +TEST_F(Vector2DTest, deleteRows_at_end_works) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + + vec.deleteRows(oldRows - 2, 2); + + EXPECT_EQ(vec.rows(), oldRows - 2); + EXPECT_EQ(vec.cols(), oldCols); + + // All remaining rows should have original content + for (size_t r = 0; r < vec.rows(); ++r) { + for (size_t c = 0; c < oldCols; ++c) { + EXPECT_EQ(vec.element(r, c), static_cast(r * COLS_COUNT_INIT + c)); + } + } +} + +TEST_F(Vector2DTest, deleteRows_beyond_bounds_is_limited) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + size_t count = 10; + + vec.deleteRows(oldRows - 2, count); + + EXPECT_EQ(vec.rows(), oldRows - count); + EXPECT_EQ(vec.cols(), oldCols); + + // All remaining rows should have original content + for (size_t r = 0; r < oldRows; ++r) { + for (size_t c = 0; c < vec.cols(); ++c) { + EXPECT_EQ(vec.element(r, c), static_cast(r * COLS_COUNT_INIT + c)); + } + } + + // All new rows should have original content + for (size_t r = oldRows; r < vec.rows(); ++r) { + for (size_t c = 0; c < vec.cols(); ++c) { + EXPECT_EQ(vec.element(r, c), static_cast((r+count) * COLS_COUNT_INIT + c)); + } + } +} + +TEST_F(Vector2DTest, deleteRows_invalid_start_does_nothing) { + auto oldVec = vec; + vec.deleteRows(oldVec.rows() + 10, 2); + EXPECT_EQ(vec, oldVec); +} + +TEST_F(Vector2DTest, deleteRows_zero_count_does_nothing) { + auto oldVec = vec; + vec.deleteRows(5, 0); + EXPECT_EQ(vec, oldVec); +} + +TEST_F(Vector2DTest, deleteRows_all_rows_creates_empty) { + vec.deleteRows(0, vec.rows()); + EXPECT_TRUE(vec.isEmpty()); + EXPECT_EQ(vec.rows(), 0); + EXPECT_EQ(vec.cols(), 0); +} + +// ==================== DELETE COLUMNS TESTS ==================== + +TEST_F(Vector2DTest, deleteColumns_removes_columns_from_middle) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + + vec.deleteColumns(4, 3); + + EXPECT_EQ(vec.rows(), oldRows); + EXPECT_EQ(vec.cols(), oldCols - 3); + + // Columns before deleted remain + for (size_t r = 0; r < oldRows; ++r) { + for (size_t c = 0; c < 4; ++c) { + EXPECT_EQ(vec.element(r, c), static_cast(r * COLS_COUNT_INIT + c)); + } + } + + // Columns after deleted shifted left + for (size_t r = 0; r < oldRows; ++r) { + for (size_t c = 4; c < vec.cols(); ++c) { + EXPECT_EQ(vec.element(r, c), static_cast(r * COLS_COUNT_INIT + c + 3)); + } + } +} + +TEST_F(Vector2DTest, deleteColumns_at_end_works) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + + vec.deleteColumns(oldCols - 2, 2); + + EXPECT_EQ(vec.rows(), oldRows); + EXPECT_EQ(vec.cols(), oldCols - 2); + + // All remaining columns should have original content + for (size_t r = 0; r < oldRows; ++r) { + for (size_t c = 0; c < vec.cols(); ++c) { + EXPECT_EQ(vec.element(r, c), static_cast(r * COLS_COUNT_INIT + c)); + } + } +} + +TEST_F(Vector2DTest, deleteColumns_beyond_bounds_is_limited) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + + vec.deleteColumns(oldCols - 2, 10); + + EXPECT_EQ(vec.rows(), oldRows); + EXPECT_EQ(vec.cols(), oldCols - 2); +} + +TEST_F(Vector2DTest, deleteColumns_invalid_start_does_nothing) { + auto oldVec = vec; + vec.deleteColumns(oldVec.cols() + 10, 2); + EXPECT_EQ(vec, oldVec); +} + +TEST_F(Vector2DTest, deleteColumns_zero_count_does_nothing) { + auto oldVec = vec; + vec.deleteColumns(5, 0); + EXPECT_EQ(vec, oldVec); +} + +TEST_F(Vector2DTest, deleteColumns_all_columns_preserves_rows) { + vec.deleteColumns(0, vec.cols()); + EXPECT_EQ(vec.rows(), ROWS_COUNT_INIT); + EXPECT_EQ(vec.cols(), 0); + EXPECT_EQ(vec.size(), 0); +} + // ==================== ADD COLUMN TESTS ==================== TEST_F(Vector2DTest, addColumn_appends_column_to_empty) { @@ -400,7 +674,7 @@ TEST_F(Vector2DTest, addColumn_appends_column_to_existing) { size_t oldRows = vec.rows(); size_t oldCols = vec.cols(); - PIVector newCol(oldRows, 999); + PIVector newCol(oldRows, [](size_t i){return -900 - (int)i;}); vec.addColumn(newCol); EXPECT_EQ(vec.rows(), oldRows); @@ -415,7 +689,7 @@ TEST_F(Vector2DTest, addColumn_appends_column_to_existing) { // Check new column for (size_t r = 0; r < oldRows; ++r) { - EXPECT_EQ(vec.element(r, oldCols), 999); + EXPECT_EQ(vec.element(r, oldCols), -900 - (int)r); } } @@ -497,7 +771,6 @@ TEST_F(Vector2DTest, addColumn_with_Col_proxy_works) { } for (size_t r = 0; r < vec.rows(); ++r) { // EXPECT_EQ(vec.element(r, oldVec.cols()), int()); - piCout << r << vec.cols() << oldVec.cols() << colIndex; EXPECT_EQ(vec.element(r, oldVec.cols()), oldVec.element(r, colIndex)); } }