diff --git a/libs/main/containers/pivector2d.h b/libs/main/containers/pivector2d.h index 537ded76..ed8d0dc6 100644 --- a/libs/main/containers/pivector2d.h +++ b/libs/main/containers/pivector2d.h @@ -705,6 +705,11 @@ public: size_t st_, sz_; public: + inline RowConst(const PIVector2D::Row & r): p_(r.p_) { + st_ = r.st_; + sz_ = r.sz_; + } + //! \~english Size of the row (number of columns). //! \~russian Размер строки (количество столбцов). inline size_t size() const { return sz_; } @@ -875,6 +880,12 @@ public: size_t step_, col_, sz_; public: + inline ColConst(const PIVector2D::Col & c): p_(c.p_) { + step_ = c.step_; + col_ = c.col_; + sz_ = c.sz_; + } + //! \~english Size of the column (number of rows). //! \~russian Размер столбца (количество строк). inline size_t size() const { return sz_; } @@ -1130,18 +1141,6 @@ public: //! \~russian Если массив был пуст, количество столбцов устанавливается равным размеру исходной строки. //! В противном случае копируется только `min(cols(), other.size())` элементов; остальные элементы новой строки инициализируются по //! умолчанию. \sa PIVector::push_back() - inline PIVector2D & addRow(const Row & other) { - if (cols_ == 0) cols_ = other.sz_; - const size_t sz = piMin(cols_, other.sz_); - const size_t ps = mat.size(); - mat.resize(mat.size() + cols_); - mat._copyRaw(mat.data(ps), other.data(), sz); - rows_++; - return *this; - } - - //! \~english Appends a new row to the bottom of the array from a read-only RowConst object. - //! \~russian Добавляет новую строку в конец массива из объекта RowConst только для чтения. inline PIVector2D & addRow(const RowConst & other) { if (cols_ == 0) cols_ = other.sz_; const size_t sz = piMin(cols_, other.sz_); @@ -1164,6 +1163,72 @@ public: return *this; } + //! \~english Appends a new column to the right of the array from a \a PIVector. + //! \~russian Добавляет новую строку в конец массива из \a PIVector. + inline PIVector2D & addColumn(const ColConst & 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]; + } + 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()) { + dst[oldCols] = other[r]; + } else { + dst[oldCols] = T(); + } + } + mat[oldCols] = other[0]; + + cols_ = newCols; + return *this; + } + + 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]; + } + 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()) { + dst[oldCols] = other[r]; + } else { + dst[oldCols] = T(); + } + } + mat[oldCols] = other[0]; + + cols_ = newCols; + return *this; + } + //! \~english Resizes the 2D array to new dimensions. //! \~russian Изменяет размер двумерного массива. //! \param rows New number of rows. diff --git a/tests/math/testpivector2d.cpp b/tests/math/testpivector2d.cpp index 29831aab..c8d3bb87 100644 --- a/tests/math/testpivector2d.cpp +++ b/tests/math/testpivector2d.cpp @@ -379,6 +379,146 @@ TEST_F(Vector2DTest, addRow_with_shorter_vector_uses_min) { } } +// ==================== ADD COLUMN TESTS ==================== + +TEST_F(Vector2DTest, addColumn_appends_column_to_empty) { + PIVector2D empty; + PIVector newCol(5); + for (size_t i = 0; i < 5; ++i) + newCol[i] = static_cast(100 + i); + + empty.addColumn(newCol); + + EXPECT_EQ(empty.rows(), 5); + EXPECT_EQ(empty.cols(), 1); + for (size_t r = 0; r < 5; ++r) { + EXPECT_EQ(empty.element(r, 0), static_cast(100 + r)); + } +} + +TEST_F(Vector2DTest, addColumn_appends_column_to_existing) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + + PIVector newCol(oldRows, 999); + vec.addColumn(newCol); + + EXPECT_EQ(vec.rows(), oldRows); + EXPECT_EQ(vec.cols(), oldCols + 1); + + // Check that old data is 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)); + } + } + + // Check new column + for (size_t r = 0; r < oldRows; ++r) { + EXPECT_EQ(vec.element(r, oldCols), 999); + } +} + +TEST_F(Vector2DTest, addColumn_with_shorter_vector_uses_min) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + size_t shortLen = oldRows - 10; + PIVector shortCol(shortLen, 777); + + vec.addColumn(shortCol); + + EXPECT_EQ(vec.cols(), oldCols + 1); + + // First shortLen rows should be 777 + for (size_t r = 0; r < shortLen; ++r) { + EXPECT_EQ(vec.element(r, oldCols), 777); + } + // Remaining rows should be default-initialized (0) + for (size_t r = shortLen; r < oldRows; ++r) { + EXPECT_EQ(vec.element(r, oldCols), 0); + } +} + +TEST_F(Vector2DTest, addColumn_with_longer_vector_truncates) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + size_t longLen = oldRows + 10; + PIVector longCol(longLen, 555); + + vec.addColumn(longCol); + + EXPECT_EQ(vec.cols(), oldCols + 1); + + // All rows should be 555 (only first oldRows elements are used) + for (size_t r = 0; r < oldRows; ++r) { + EXPECT_EQ(vec.element(r, oldCols), 555); + } +} + +TEST_F(Vector2DTest, addColumn_with_empty_source_does_nothing_on_empty) { + PIVector2D empty; + PIVector emptyCol; + + empty.addColumn(emptyCol); + EXPECT_TRUE(empty.isEmpty()); + EXPECT_EQ(empty.rows(), 0); + EXPECT_EQ(empty.cols(), 0); +} + +TEST_F(Vector2DTest, addColumn_with_empty_source_adds_default_column) { + auto oldVec = vec; + + vec.addColumn({}); + + EXPECT_EQ(vec.cols(), oldVec.cols()); + EXPECT_EQ(vec.rows(), oldVec.rows()); + + for (size_t r = 0; r < oldVec.rows(); ++r) { + for (size_t c = 0; c < oldVec.cols(); ++c) { + EXPECT_EQ(vec.element(r, c), oldVec.element(r, c)); + } + } +} + +TEST_F(Vector2DTest, addColumn_with_Col_proxy_works) { + auto oldVec = vec; + const size_t colIndex = 5; + + auto srcCol = oldVec.col(colIndex); + vec.addColumn(srcCol); + + EXPECT_EQ(vec.cols(), oldVec.cols() + 1); + EXPECT_EQ(vec.rows(), oldVec.rows()); + + for (size_t r = 0; r < oldVec.rows(); ++r) { + for (size_t c = 0; c < oldVec.cols(); ++c) { + EXPECT_EQ(vec.element(r, c), oldVec.element(r, c)); + } + } + 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)); + } +} + +TEST_F(Vector2DTest, addColumn_with_ColConst_proxy_works) { + size_t oldRows = vec.rows(); + size_t oldCols = vec.cols(); + + const auto & constVec = vec; + auto srcCol = constVec.col(7); + // Need a non-const array to add to + PIVector2D mutableVec = vec; // copy + mutableVec.addColumn(srcCol); + + EXPECT_EQ(mutableVec.cols(), oldCols + 1); + + for (size_t r = 0; r < oldRows; ++r) { + EXPECT_EQ(mutableVec.element(r, oldCols), vec.element(r, 7)); + } +} + // ==================== RESIZE TESTS ==================== class Vector2DResizeTest: public Vector2DTest { protected: @@ -670,7 +810,8 @@ TEST(Vector2DTransposeTest, singleElement_returnsSame) { TEST(Vector2DTransposeTest, oneRow_becomesOneColumn) { PIVector2D rowVec(1, 5); - for (size_t c = 0; c < 5; ++c) rowVec.element(0, c) = static_cast(c); + for (size_t c = 0; c < 5; ++c) + rowVec.element(0, c) = static_cast(c); auto transposed = rowVec.transposed(); EXPECT_EQ(transposed.rows(), 5); EXPECT_EQ(transposed.cols(), 1); @@ -681,7 +822,8 @@ TEST(Vector2DTransposeTest, oneRow_becomesOneColumn) { TEST(Vector2DTransposeTest, oneColumn_becomesOneRow) { PIVector2D colVec(5, 1); - for (size_t r = 0; r < 5; ++r) colVec.element(r, 0) = static_cast(r); + for (size_t r = 0; r < 5; ++r) + colVec.element(r, 0) = static_cast(r); auto transposed = colVec.transposed(); EXPECT_EQ(transposed.rows(), 1); EXPECT_EQ(transposed.cols(), 5); @@ -691,7 +833,7 @@ TEST(Vector2DTransposeTest, oneColumn_becomesOneRow) { } TEST_F(Vector2DTest, transposed_doesNotModifyOriginal) { - auto original = vec; // копия для сравнения + auto original = vec; // копия для сравнения auto transposed = vec.transposed(); // Проверяем, что исходный массив не изменился EXPECT_EQ(vec, original); @@ -1271,7 +1413,6 @@ TEST_F(Vector2DTest, colconst_proxy_every_returns_true_if_all_match) { auto isLessThanMax = [&](const int & e) { return e < static_cast(vec.size()); }; EXPECT_TRUE(col.every(isLessThanMax)); auto isNotEven = [](const int & e) { return e % 2 != 0; }; - col.forEach([](const int & v) { piCout << v; }); EXPECT_FALSE(col.every(isNotEven)); }