//! \addtogroup Containers
//! \{
//! \~\file pivector2d.h
//! \brief
//! \~english Declares \a PIVector2D
//! \~russian Объявление \a PIVector2D
//! \~\authors
//! \~english
//! Andrey Bychkov work.a.b@yandex.ru;
//! \~russian
//! Андрей Бычков work.a.b@yandex.ru;
//! \~\}
/*
    PIP - Platform Independent Primitives
    2D wrapper around PIVector
    Andrey Bychkov work.a.b@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PIVECTOR2D_H
#define PIVECTOR2D_H

#include "pivector.h"

//! \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
//! Этот класс используется для хранения двумерного массива элементов любого типа в виде единого непрерывного блока памяти (обычного
//! \a PIVector). Доступ к элементам осуществляется с помощью операторов `[][]`, где первый индекс — это строка, а второй — столбец. Со
//! строками можно работать как с объектами \a PIVector, что позволяет изменять отдельные элементы или присваивать целые строки. Нельзя
//! напрямую добавлять или удалять элементы, чтобы изменить размеры массива после создания (используйте \a resize(), \a addRow(), \a
//! removeRow(), \a removeColumn() для этого), но можно изменять значения существующих элементов.


template<typename T>
class PIVector2D {
public:
	//! \brief
	//! \~english Index structure for 2D array elements (row, column).
	//! \~russian Структура индекса для элементов двумерного массива (строка, столбец).
	struct Index {
		//! \~english Row index in the 2D array.
		//! \~russian Индекс строки в двумерном массиве.
		ssize_t row    = -1;
		//! \~english Column index in the 2D array.
		//! \~russian Индекс столбца в двумерном массиве.
		ssize_t col    = -1;

		//! \~english Default constructor. Initializes row and col to -1 (invalid index).
		//! \~russian Конструктор по умолчанию. Инициализирует row и col значениями -1 (некорректный индекс).
		inline Index() = default;
		//! \~english Constructs an Index with the given row and column values.
		//! \~russian Создаёт Index с заданными значениями строки и столбца.
		inline Index(ssize_t r, ssize_t c): row(r), col(c) {}

		//! \~english Checks if the index is valid (both row and column are non-negative).
		//! \~russian Проверяет, является ли индекс корректным (строка и столбец неотрицательны).
		//! \~\sa isNotValid()
		inline bool isValid() const { return row >= 0 && col >= 0; }

		//! \~english Checks if the index is invalid (either row or column is negative).
		//! \~russian Проверяет, является ли индекс некорректным (строка или столбец отрицательны).
		//! \~\sa isValid()
		inline bool isNotValid() const { return !isValid(); }
	};

	//! \~english Constructs an empty 2D array. No memory is allocated.
	//! \~russian Создаёт пустой двумерный массив. Память не выделяется.
	//! \details
	//! \~english After this constructor, \a rows() and \a cols() return 0, and \a isEmpty() returns true.
	//! \~russian После этого конструктора \a rows() и \a cols() возвращают 0, а \a isEmpty() возвращает true.
	//! \~\sa PIVector::PIVector()
	inline PIVector2D() { rows_ = cols_ = 0; }

	//! \~english Constructs a 2D array with the given dimensions, filled with copies of `f`.
	//! \~russian Создаёт двумерный массив заданного размера, заполненный копиями `f`.
	//! \details
	//! \~english The underlying storage is a single contiguous block of memory of size `rows * cols`.
	//! All elements are initialized with the value `f`.
	//! \~russian Внутреннее хранилище представляет собой единый непрерывный блок памяти размером `rows * cols`.
	//! Все элементы инициализируются значением `f`.
	//! \~\sa PIVector::PIVector(size_t, const T&)
	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 Создаёт двумерный массив из существующего одномерного вектора, изменяя его форму.
	//! \details
	//! \~english The constructor copies the data from `v` into the internal flat vector.
	//! If `v` is larger than `rows * cols`, the excess elements are ignored (the vector is truncated).
	//! If `v` is smaller, other values filled whith default cunstructor T()
	//! \~russian Конструктор копирует данные из `v` во внутренний плоский вектор.
	//! Если `v` больше, чем `rows * cols`, лишние элементы игнорируются (вектор обрезается).
	//! Если `v` меньше, остальные значения будут заполнены из конструктора по умолчанию T()
	//! \~\sa PIVector::PIVector(const PIVector&), reshape()
	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 Конструктор перемещения из существующего одномерного вектора, изменяя его форму.
	//! \details
	//! \~english The data is moved from `v` into the internal flat vector, avoiding a copy.
	//! After construction, `v` is left in a valid but unspecified state.
	//! \~russian Данные перемещаются из `v` во внутренний плоский вектор, что позволяет избежать копирования.
	//! После завершения конструктора `v` остаётся в корректном, но неопределённом состоянии.
	//! \~\sa PIVector::PIVector(PIVector&&)
	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 Создаёт двумерный массив из вектора векторов (рваного массива). Предполагается, что все внутренние векторы имеют
	//! одинаковый размер.
	//! \details
	//! \~english If the input is empty, the constructed array is also empty. Otherwise, the number of columns is taken from the size of the
	//! first inner vector. All inner vectors are concatenated in the internal flat storage.
	//! \~russian Если входной массив пуст, создаётся пустой двумерный массив. В противном случае количество столбцов берётся из размера
	//! первого внутреннего вектора. Все внутренние векторы конкатенируются во внутреннем плоском хранилище. \sa PIVector::append()
	inline PIVector2D(const PIVector<PIVector<T>> & v) {
		rows_ = v.size();
		if (rows_) {
			cols_ = v[0].size();
			mat.reserve(rows_ * cols_);
			for (size_t i = 0; i < rows_; i++) {
				mat.append(v[i]);
			}
			mat.resize(rows_ * cols_);
		}
		if (mat.isEmpty()) rows_ = cols_ = 0;
	}

	//! \~english Returns the number of rows in the 2D array.
	//! \~russian Возвращает количество строк в двумерном массиве.
	//! \return Number of rows.
	//! \details
	//! \~english The result is always non-negative. If the array is empty, returns 0.
	//! \~russian Результат всегда неотрицательный. Если массив пуст, возвращает 0.
	//! \~\sa cols(), size(), PIVector::size()
	inline size_t rows() const { return rows_; }

	//! \~english Returns the number of columns in the 2D array.
	//! \~russian Возвращает количество столбцов в двумерном массиве.
	//! \return Number of columns.
	//! \details
	//! \~english The result is always non-negative. If the array is empty, returns 0.
	//! \~russian Результат всегда неотрицательный. Если массив пуст, возвращает 0.
	//! \~\sa rows(), size(), PIVector::size()
	inline size_t cols() const { return cols_; }

	//! \~english Returns the total number of elements (`rows * cols`).
	//! \~russian Возвращает общее количество элементов (`строки * столбцы`).
	//! \return Total number of elements.
	//! \details
	//! \~english This is equivalent to the size of the underlying flat vector.
	//! \~russian Это эквивалентно размеру внутреннего плоского вектора.
	//! \~\sa rows(), cols(), PIVector::size()
	inline size_t size() const { return mat.size(); }

	//! \~english Returns the total number of elements as a signed value.
	//! \~russian Возвращает общее количество элементов в виде знакового числа.
	//! \return Signed size.
	//! \~\sa size(), PIVector::size_s()
	inline ssize_t size_s() const { return mat.size_s(); }

	//! \~english Returns the total number of elements (same as \a size()).
	//! \~russian Возвращает общее количество элементов (то же, что и \a size()).
	//! \return Total number of elements.
	//! \~\sa size(), PIVector::length()
	inline size_t length() const { return mat.length(); }

	//! \~english Returns the number of elements that the underlying container has currently allocated space for.
	//! \~russian Возвращает количество элементов, для которого сейчас выделена память во внутреннем контейнере.
	//! \return Capacity of the flat vector.
	//! \details
	//! \~english This value may be larger than \a size(). It indicates how many elements can be added before a reallocation is needed.
	//! \~russian Это значение может быть больше, чем \a size(). Оно показывает, сколько элементов можно добавить до того, как потребуется
	//! перераспределение памяти. \sa reserve(), PIVector::capacity()
	inline size_t capacity() const { return mat.capacity(); }

	//! \~english Checks if the array has no elements.
	//! \~russian Проверяет, пуст ли массив.
	//! \return \c true if the array is empty, \c false otherwise.
	//! \details
	//! \~english An empty array has both rows and columns equal to 0.
	//! \~russian Пустой массив имеет и строки, и столбцы равные 0.
	//! \~\sa isNotEmpty(), PIVector::isEmpty()
	inline bool isEmpty() const { return mat.isEmpty(); }

	//! \~english Checks if the array has at least one element.
	//! \~russian Проверяет, не пуст ли массив.
	//! \return \c true if the array is not empty, \c false otherwise.
	//! \~\sa isEmpty(), PIVector::isNotEmpty()
	inline bool isNotEmpty() const { return mat.isNotEmpty(); }

	class RowConst;
	class ColConst;
	class Row;
	class Col;


	//! \class RowConst
	//! \brief
	//! \~english Proxy class representing a single read-only row in a \a PIVector2D.
	//! \~russian Прокси-класс, представляющий одну строку в \a PIVector2D только для чтения.
	//! \details
	//! \~english Returned by const \a operator[] or \a row(). Provides const access to row elements.
	//! \~russian Возвращается константными версиями \a operator[] или \a row(). Предоставляет константный доступ к элементам строки.
	//! \~\sa Row, ColConst
	class RowConst {
		friend class PIVector2D<T>;

	protected:
		inline RowConst(const PIVector2D<T> * p, size_t row): p_(&(p->mat)), st_(p->cols_ * row), sz_(p->cols_) {}
		const PIVector<T> * p_;
		const size_t st_, sz_;

	public:
		//! \~english Copy constructor from modifiable Row to read-only RowConst.
		//! \~russian Конструктор копирования из модифицируемого класса Row в константный RowConst.
		//! \~\sa Row
		inline RowConst(const PIVector2D<T>::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_; }

		//! \~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_); }

		//! \~english Returns the first index of element `e` in the row, starting from `start`.
		//! \~russian Возвращает первый индекс элемента `e` в строке, начиная с позиции `start`.
		//! \details
		//! \~english See \a PIVector::indexOf() for details on negative start handling.
		//! \~russian Подробнее об обработке отрицательного `start` см. \a PIVector::indexOf().
		//! \~\sa PIVector::indexOf()
		inline ssize_t indexOf(const T & e, ssize_t start = 0) const {
			if (start < 0) start = 0;
			for (size_t i = (size_t)start; i < sz_; ++i) {
				if ((*p_)[st_ + i] == e) return (ssize_t)i;
			}
			return -1;
		}

		//! \~english Returns the last index of element `e` in the row, searching backwards from `start`.
		//! \~russian Возвращает последний индекс элемента `e` в строке, выполняя поиск в обратном направлении от `start`.
		//! \return Index if found, -1 otherwise.
		//! \~\sa PIVector::lastIndexOf()
		inline ssize_t lastIndexOf(const T & e, ssize_t start = -1) const {
			ssize_t from = (start < 0 || (size_t)start >= sz_) ? (ssize_t)sz_ - 1 : start;
			for (ssize_t i = from; i >= 0; --i) {
				if ((*p_)[st_ + i] == e) return i;
			}
			return -1;
		}

		//! \~english Returns the first index where the predicate `test` returns true, starting from `start`.
		//! \~russian Возвращает первый индекс, для которого предикат `test` возвращает true, начиная с `start`.
		//! \~\sa PIVector::indexWhere()
		inline ssize_t indexWhere(std::function<bool(const T & e)> test, ssize_t start = 0) const {
			if (start < 0) start = 0;
			for (size_t i = (size_t)start; i < sz_; ++i) {
				if (test((*p_)[st_ + i])) return (ssize_t)i;
			}
			return -1;
		}

		//! \~english Returns the last index where the predicate `test` returns true, searching backwards from `start`.
		//! \~russian Возвращает последний индекс, для которого предикат `test` возвращает true,
		//! выполняя поиск в обратном направлении от `start`.
		//! \~\sa PIVector::lastIndexWhere()
		inline ssize_t lastIndexWhere(std::function<bool(const T & e)> test, ssize_t start = -1) const {
			ssize_t from = (start < 0 || (size_t)start >= sz_) ? (ssize_t)sz_ - 1 : start;
			for (ssize_t i = from; i >= 0; --i) {
				if (test((*p_)[st_ + i])) return i;
			}
			return -1;
		}

		//! \~english Applies a function to each element of the row (read-only).
		//! \~russian Применяет функцию к каждому элементу строки (только чтение).
		//! \details
		//! \~english The function can't modify the elements.
		//! \~russian Функция не может изменять элементы.
		//! \~\sa forEach (modifiable)
		inline void forEach(std::function<void(const T &)> func) const {
			for (size_t i = 0; i < sz_; ++i) {
				func((*p_)[st_ + i]);
			}
		}

		//! \~english Checks if the row contains the element `e`.
		//! \~russian Проверяет, содержит ли строка элемент `e`.
		//! \~\sa PIVector::contains()
		inline bool contains(const T & e, ssize_t start = 0) const { return indexOf(e, start) != -1; }

		//! \~english Counts occurrences of `e` in the row.
		//! \~russian Подсчитывает количество вхождений `e` в строке.
		//! \~\sa PIVector::entries()
		inline int entries(const T & e, ssize_t start = 0) const {
			if (start < 0) start = 0;
			int count = 0;
			for (size_t i = (size_t)start; i < sz_; ++i) {
				if ((*p_)[st_ + i] == e) ++count;
			}
			return count;
		}

		//! \~english Counts elements in the row that pass the `test`.
		//! \~russian Подсчитывает элементы в строке, проходящие `test`.
		//! \~\sa PIVector::entries(std::function)
		inline int entries(std::function<bool(const T & e)> test, ssize_t start = 0) const {
			if (start < 0) start = 0;
			int count = 0;
			for (size_t i = (size_t)start; i < sz_; ++i) {
				if (test((*p_)[st_ + i])) ++count;
			}
			return count;
		}

		//! \~english Tests if any element in the row passes the `test`.
		//! \~russian Проверяет, проходит ли какой-либо элемент в строке `test`.
		//! \~\sa PIVector::any()
		inline bool any(std::function<bool(const T & e)> test) const {
			for (size_t i = 0; i < sz_; ++i) {
				if (test((*p_)[st_ + i])) return true;
			}
			return false;
		}

		//! \~english Tests if all elements in the row pass the `test`.
		//! \~russian Проверяет, проходят ли все элементы в строке `test`.
		//! \~\sa PIVector::every()
		inline bool every(std::function<bool(const T & e)> test) const {
			for (size_t i = 0; i < sz_; ++i) {
				if (!test((*p_)[st_ + i])) return false;
			}
			return true;
		}
	};


	//! \class ColConst
	//! \brief
	//! \~english Proxy class representing a single read-only column in a \a PIVector2D.
	//! \~russian Прокси-класс, представляющий один столбец в \a PIVector2D только для чтения.
	//! \details
	//! \~english Returned by const \a col(). Provides const access to column elements.
	//! \~russian Возвращается константной версией \a col(). Предоставляет константный доступ к элементам столбца.
	//! \~\sa Col, RowConst
	class ColConst {
		friend class PIVector2D<T>;

	protected:
		inline ColConst(const PIVector2D<T> * p, size_t col): p_(&(p->mat)), step_(p->cols_), col_(col), sz_(p->rows_) {}
		const PIVector<T> * p_;
		const size_t step_, col_, sz_;

	public:
		//! \~english Copy constructor from modifiable Col to read-only ColConst.
		//! \~russian Конструктор копирования из модифицируемого класса Col в константный ColConst.
		//! \~\sa Col
		inline ColConst(const PIVector2D<T>::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_; }

		//! \~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 (size_t i = 0; i < size(); i++)
				ret << (*p_)[i * step_ + col_];
			return ret;
		}

		//! \~english Returns the first index of element `e` in the row, starting from `start`.
		//! \~russian Возвращает первый индекс элемента `e` в строке, начиная с позиции `start`.
		//! \details
		//! \~english See \a PIVector::indexOf() for details on negative start handling.
		//! \~russian Подробнее об обработке отрицательного `start` см. \a PIVector::indexOf().
		//! \~\sa PIVector::indexOf()
		inline ssize_t indexOf(const T & e, ssize_t start = 0) const {
			if (start < 0) start = 0;
			for (size_t i = (size_t)start; i < sz_; ++i) {
				if ((*p_)[i * step_ + col_] == e) return (ssize_t)i;
			}
			return -1;
		}

		//! \~english Returns the last index of element `e` in the row, searching backwards from `start`.
		//! \~russian Возвращает последний индекс элемента `e` в строке, выполняя поиск в обратном направлении от `start`.
		//! \~\sa PIVector::lastIndexOf()
		inline ssize_t lastIndexOf(const T & e, ssize_t start = -1) const {
			ssize_t from = (start < 0 || (size_t)start >= sz_) ? (ssize_t)sz_ - 1 : start;
			for (ssize_t i = from; i >= 0; --i) {
				if ((*p_)[i * step_ + col_] == e) return i;
			}
			return -1;
		}

		//! \~english Returns the first index where the predicate `test` returns true, starting from `start`.
		//! \~russian Возвращает первый индекс, для которого предикат `test` возвращает true, начиная с `start`.
		//! \~\sa PIVector::indexWhere()
		inline ssize_t indexWhere(std::function<bool(const T & e)> test, ssize_t start = 0) const {
			if (start < 0) start = 0;
			for (size_t i = (size_t)start; i < sz_; ++i) {
				if (test((*p_)[i * step_ + col_])) return (ssize_t)i;
			}
			return -1;
		}

		//! \~english Returns the last index where the predicate `test` returns true, searching backwards from `start`.
		//! \~russian Возвращает последний индекс, для которого предикат `test` возвращает true,
		//! выполняя поиск в обратном направлении от `start`.
		//! \~\sa PIVector::lastIndexWhere()
		inline ssize_t lastIndexWhere(std::function<bool(const T & e)> test, ssize_t start = -1) const {
			ssize_t from = (start < 0 || (size_t)start >= sz_) ? (ssize_t)sz_ - 1 : start;
			for (ssize_t i = from; i >= 0; --i) {
				if (test((*p_)[i * step_ + col_])) return i;
			}
			return -1;
		}

		//! \~english Applies a function to each element of the column (read-only).
		//! \~russian Применяет функцию к каждому элементу столбца (только чтение).
		//! \details
		//! \~english The function can't modify the elements.
		//! \~russian Функция не может изменять элементы.
		//! \~\sa forEach (modifiable)
		inline void forEach(std::function<void(const T &)> func) const {
			for (size_t i = 0; i < sz_; ++i) {
				func((*p_)[i * step_ + col_]);
			}
		}

		//! \~english Checks if the column contains the element `e`.
		//! \~russian Проверяет, содержит ли столбец элемент `e`.
		//! \~\sa PIVector::contains()
		inline bool contains(const T & e, ssize_t start = 0) const { return indexOf(e, start) != -1; }

		//! \~english Counts occurrences of `e` in the column.
		//! \~russian Подсчитывает количество вхождений `e` в столбце.
		//! \~\sa PIVector::entries()
		inline int entries(const T & e, ssize_t start = 0) const {
			if (start < 0) start = 0;
			int count = 0;
			for (size_t i = (size_t)start; i < sz_; ++i) {
				if ((*p_)[i * step_ + col_] == e) ++count;
			}
			return count;
		}

		//! \~english Counts elements in the column that pass the `test`.
		//! \~russian Подсчитывает элементы в столбце, проходящие `test`.
		//! \~\sa PIVector::entries(std::function)
		inline int entries(std::function<bool(const T & e)> test, ssize_t start = 0) const {
			if (start < 0) start = 0;
			int count = 0;
			for (size_t i = (size_t)start; i < sz_; ++i) {
				if (test((*p_)[i * step_ + col_])) ++count;
			}
			return count;
		}

		//! \~english Tests if any element in the column passes the `test`.
		//! \~russian Проверяет, проходит ли какой-либо элемент в столбце `test`.
		//! \~\sa PIVector::any()
		inline bool any(std::function<bool(const T & e)> test) const {
			for (size_t i = 0; i < sz_; ++i) {
				if (test((*p_)[i * step_ + col_])) return true;
			}
			return false;
		}

		//! \~english Tests if all elements in the column pass the `test`.
		//! \~russian Проверяет, проходят ли все элементы в столбце `test`.
		//! \~\sa PIVector::every()
		inline bool every(std::function<bool(const T & e)> test) const {
			for (size_t i = 0; i < sz_; ++i) {
				if (!test((*p_)[i * step_ + col_])) return false;
			}
			return true;
		}
	};

	//! \class Row
	//! \brief
	//! \~english Proxy class representing a single row in a \a PIVector2D for modification.
	//! \~russian Прокси-класс, представляющий одну строку в \a PIVector2D для модификации.
	//! \details
	//! \~english Objects of this class are returned by non-const \a operator[] or \a row().
	//! They provide array-like access to the elements of a specific row and allow operations such as assignment from another row or a \a
	//! PIVector, searching, filling, and iteration.
	//! \~russian Объекты этого класса возвращаются неконстантными операторами \a operator[] или методом \a row().
	//! Они предоставляют доступ к элементам конкретной строки, подобный массиву, и позволяют выполнять такие операции, как присваивание из
	//! другой строки или \a PIVector, поиск, заполнение и итерацию. \sa Col, RowConst
	class Row: public RowConst {
		friend class PIVector2D<T>;

	private:
		inline Row(PIVector2D<T> * p, size_t row): RowConst(p, row), p_(&(p->mat)) {}
		PIVector<T> * p_;

	public:
		using RowConst::operator[];
		using RowConst::data;
		using RowConst::size;

		//! \~english Accesses the element at the given column index within the row.
		//! \~russian Доступ к элементу по заданному индексу столбца в строке.
		//! \details
		//! \~english No bounds checking is performed; use with caution.
		//! \~russian Проверка границ не выполняется; используйте с осторожностью.
		//! \~\sa PIVector::operator[]
		inline T & operator[](size_t index) { return (*p_)[this->st_ + index]; }

		//! \~english Returns a pointer to the row data starting at an optional offset.
		//! \~russian Возвращает указатель на данные строки, начиная с опционального смещения.
		//! \details
		//! \~english The pointer can be used for direct memory operations. It remains valid as long as the underlying 2D array is not
		//! reallocated.
		//! \~russian Указатель можно использовать для прямых операций с памятью. Он остаётся действительным, пока не произойдёт
		//! перераспределение памяти внутреннего двумерного массива. \sa PIVector::data()
		inline T * data(size_t index = 0) { return p_->data(this->st_ + index); }

		//! \~english Assigns the contents of another Row to this row.
		//! \~russian Присваивает этой строке содержимое другой строки.
		//! \details
		//! \~english Only the minimum of the two row sizes is copied; if this row is shorter, excess elements in `other` are ignored.
		//! \~russian Копируется только минимум из размеров двух строк; если эта строка короче, лишние элементы из `other` игнорируются.
		//! \~\sa PIVector::operator=
		inline Row & operator=(const Row & other) {
			if (p_ == other.p_ && this->st_ == other.st_) return *this;
			const size_t sz = piMin<size_t>(this->sz_, other.sz_);
			p_->_copyRaw(p_->data(this->st_), other.data(), sz);
			return *this;
		}

		//! \~english Assigns the contents of a \a PIVector to this row.
		//! \~russian Присваивает этой строке содержимое \a PIVector.
		//! \details
		//! \~english Only the minimum of the row size and vector size is copied.
		//! \~russian Копируется только минимум из размера строки и размера вектора.
		//! \~\sa PIVector::operator=
		inline Row & operator=(const PIVector<T> & other) {
			const size_t sz = piMin<size_t>(this->sz_, other.size());
			p_->_copyRaw(p_->data(this->st_), other.data(), sz);
			return *this;
		}

		//! \~english Applies a function to each element of the row (modifiable).
		//! \~russian Применяет функцию к каждому элементу строки (с возможностью изменения).
		//! \param func Function that takes a reference to T.
		//! \details
		//! \~english The function can modify the elements.
		//! \~russian Функция может изменять элементы.
		//! \~\sa PIVector::forEach()
		inline void forEach(std::function<void(T &)> func) {
			for (size_t i = 0; i < this->sz_; ++i) {
				func((*p_)[this->st_ + i]);
			}
		}

		//! \~english Fills the row with copies of `value`.
		//! \~russian Заполняет строку копиями `value`.
		//! \~\sa PIVector::fill()
		inline void fill(const T & value) {
			for (size_t i = 0; i < this->sz_; ++i) {
				(*p_)[this->st_ + i] = value;
			}
		}
	};


	//! \class Col
	//! \brief
	//! \~english Proxy class representing a single column in a \a PIVector2D for modification.
	//! \~russian Прокси-класс, представляющий один столбец в \a PIVector2D для модификации.
	//! \details
	//! \~english Objects of this class are returned by non-const \a col(). They provide column-wise access and operations similar to \a
	//! Row.
	//! \~russian Объекты этого класса возвращаются неконстантным методом \a col(). Они предоставляют доступ к столбцам и операции,
	//! аналогичные \a Row. \sa Row, ColConst
	class Col: public ColConst {
		friend class PIVector2D<T>;

	private:
		inline Col(PIVector2D<T> * p, size_t col): ColConst(p, col), p_(&(p->mat)) {}
		PIVector<T> * p_;

	public:
		using ColConst::operator[];
		using ColConst::data;
		using ColConst::size;

		//! \~english Accesses the element at the given row index within the column.
		//! \~russian Доступ к элементу по заданному индексу строки в столбце.
		//! \return Reference to the element.
		inline T & operator[](size_t index) { return (*p_)[index * this->step_ + this->col_]; }

		//! \~english Returns a pointer to the column data starting at an optional row offset.
		//! \~russian Возвращает указатель на данные столбца, начиная с опционального смещения по строкам.
		//! \details
		//! \~english Note that column elements are not stored contiguously in memory, so this pointer cannot be used to iterate over the
		//! whole column.
		//! \~russian Обратите внимание, что элементы столбца не хранятся в памяти непрерывно, поэтому этот указатель нельзя использовать
		//! для итерации по всему столбцу.
		inline T * data(size_t index = 0) { return p_->data(index * this->step_ + this->col_); }

		//! \~english Assigns the contents of another Col to this column.
		//! \~russian Присваивает этому столбцу содержимое другого столбца.
		inline Col & operator=(const Col & other) {
			if (p_ == other.p_ && this->col_ == other.col_) return *this;
			const size_t sz = piMin<size_t>(this->sz_, other.sz_);
			for (size_t i = 0; i < sz; ++i)
				(*p_)[i * this->step_ + this->col_] = other[i];
			return *this;
		}

		//! \~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>(this->sz_, other.size());
			for (size_t i = 0; i < sz; ++i)
				(*p_)[i * this->step_ + this->col_] = other[i];
			return *this;
		}

		//! \~english Applies a function to each element of the column (modifiable).
		//! \~russian Применяет функцию к каждому элементу столбца (с возможностью изменения).
		//! \details
		//! \~english The function can modify the elements.
		//! \~russian Функция может изменять элементы.
		//! \~\sa PIVector::forEach()
		inline void forEach(std::function<void(T &)> func) {
			for (size_t i = 0; i < this->sz_; ++i) {
				func((*p_)[i * this->step_ + this->col_]);
			}
		}

		//! \~english Fills the column with copies of `value`.
		//! \~russian Заполняет столбец копиями `value`.
		//! \~\sa PIVector::fill()
		inline void fill(const T & value) {
			for (size_t i = 0; i < this->sz_; ++i) {
				(*p_)[i * this->step_ + this->col_] = value;
			}
		}
	};


	//! \~english Returns a reference to the element at the given row and column.
	//! \~russian Возвращает ссылку на элемент по заданной строке и столбцу.
	//! \details
	//! \~english No bounds checking is performed.
	//! \~russian Проверка границ не выполняется.
	//! \~\sa at() (const version), PIVector::operator[]
	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
	//! \~russian Возвращает константную ссылку на элемент по заданной строке и столбцу
	//! \details
	//! \~english No bounds checking is performed.
	//! \~russian Проверка границ не выполняется.
	inline const T & at(size_t row, size_t col) const { return mat[row * cols_ + col]; }

	//! \~english Returns a reference to the element at the given Index.
	//! \~russian Возвращает ссылку на элемент по заданному Index.
	inline T & operator[](const Index & idx) { return element(idx.row, idx.col); }

	//! \~english Returns a const reference to the element at the given Index.
	//! \~russian Возвращает константную ссылку на элемент по заданному Index.
	inline const T & operator[](const Index & idx) const { return element(idx.row, idx.col); }

	//! \~english Returns a reference to the element at the given Index.
	//! \~russian Возвращает ссылку на элемент по заданному Index.
	inline T & element(const Index & idx) { return element(idx.row, idx.col); }

	//! \~english Returns a const reference to the element at the given Index.
	//! \~russian Возвращает константную ссылку на элемент по заданному Index.
	inline const T & element(const Index & idx) const { return element(idx.row, idx.col); }

	//! \~english Returns a const reference to the element at the given Index (bounds-checked only in debug).
	//! \~russian Возвращает константную ссылку на элемент по заданному Index (проверка границ только в отладочном режиме).
	inline const T & at(const Index & idx) const { return at(idx.row, idx.col); }

	//! \~english Returns a proxy object for the row at the given index for modification.
	//! \~russian Возвращает прокси-объект для строки по заданному индексу для модификации.
	//! \~\sa row(), Col
	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 Возвращает указатель на внутренние плоские данные, начиная с опционального смещения.
	//! \~\sa PIVector::data()
	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 Возвращает прокси-объект для строки по заданному индексу для модификации.
	//! \~\sa operator[]
	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 Возвращает прокси-объект для столбца по заданному индексу для модификации.
	//! \~\sa col() const
	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 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.
	//! \details
	//! \~english If the array was empty, its column count is set to the size of the source row.
	//! Otherwise, only `min(cols(), other.size())` elements are copied; the rest of the new row is default-initialized.
	//! \~russian Если массив был пуст, количество столбцов устанавливается равным размеру исходной строки.
	//! В противном случае копируется только `min(cols(), other.size())` элементов; остальные элементы новой строки инициализируются по
	//! умолчанию.
	//! \~\sa PIVector::push_back()
	inline PIVector2D<T> & addRow(const RowConst & other) {
		if (cols_ == 0) cols_ = other.sz_;
		mat.append(other.toVector());
		rows_++;
		mat.resize(rows_ * cols_);
		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();
		mat.append(other);
		rows_++;
		mat.resize(rows_ * cols_);
		return *this;
	}

	//! \~english Appends \a count new empty rows to the bottom of the array, filled with value \a f.
	//! \~russian Добавляет \a count новых пустых строк в конец массива, заполненных значением \a f.
	//! \details
	//! \~english If the array was empty (no columns defined), the column count is set to 1.
	//! The new rows are filled with the default value \a f.
	//! \~russian Если массив был пуст (количество столбцов не определено), количество столбцов устанавливается равным 1.
	//! Новые строки заполняются значением по умолчанию \a f.
	//! \~\sa addRow(), appendColumns()
	inline PIVector2D<T> & 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;
	}

	//! \~english Appends \a count new empty columns to the end of each row of the array.
	//! \~russian Добавляет \a count новых пустых столбцов в конец каждой строки массива.
	//! \details
	//! \~english If the array was empty (rows not defined), the array becomes a single row with \a count columns.
	//! If the array already has rows, new elements are inserted at the end of each existing row.
	//! \~russian Если массив был пуст (строки не определены), массив становится одной строкой с \a count столбцов.
	//! Если массив уже содержит строки, новые элементы добавляются в конец каждой существующей строки.
	//! \~\sa appendRows(), addColumn()
	inline PIVector2D<T> & 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;
	}

	//! \~english Deletes `count` rows starting from the specified row index.
	//! \~russian Удаляет `count` строк, начиная с указанного индекса строки.
	//! \details
	//! \~english Removes the specified rows from the array and updates the row count. If all elements are deleted (array becomes empty),
	//! both rows and columns are set to 0.
	//! \~russian Удаляет указанные строки из массива и обновляет количество строк. Если все элементы удалены (массив становится пустым),
	//! количество строк и столбцов устанавливается в 0.
	//! \~\sa deleteColumns()
	inline PIVector2D<T> & 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;
	}

	//! \~english Removes the specified columns from the array and updates the column count.
	//! \~russian Удаляет указанные столбцы из массива и обновляет количество столбцов.
	//! \details
	//! \~english Removes \a count columns starting from \a col_start. If \a col_start is out of range or \a count is 0,
	//! the function does nothing. If \a count extends beyond the last column, only available columns are deleted.
	//! \~russian Удаляет \a count столбцов начиная с \a col_start. Если \a col_start выходит за границы или \a count равен 0,
	//! функция ничего не делает. Если \a count выходит за последний столбец, удаляются только доступные столбцы.
	//! \~\sa removeColumn(), deleteRows()
	inline PIVector2D<T> & 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) {
			mat.remove(r * (cols_ - count) + col_start, count);
		}
		cols_ -= count;
		mat.resize(rows_ * cols_);
		return *this;
	}

	//! \~english Appends a new column to the right of the array from a \a ColConst.
	//! \~russian Добавляет новую строку в конец массива из \a ColConst.
	inline PIVector2D<T> & addColumn(const ColConst & other) {
		if (other.size() == 0) return *this;
		if (isEmpty()) {
			mat.reserve(other.size());
			for (size_t r = 0; r < other.size(); ++r) {
				mat.append(other[r]);
			}
			rows_ = mat.size();
			cols_ = 1;
			return *this;
		}

		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 {
				mat.insert(r * cols_);
			}
		}

		cols_ = newCols;
		return *this;
	}

	//! \~english Appends a new column to the right of the array from a \a PIVector.
	//! \~russian Добавляет новую строку в конец массива из \a PIVector.
	inline PIVector2D<T> & addColumn(const PIVector<T> & other) {
		if (other.size() == 0) return *this;
		if (isEmpty()) {
			mat.append(other);
			rows_ = mat.size();
			cols_ = 1;
			return *this;
		}

		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 {
				mat.insert(r * cols_);
			}
		}

		cols_ = newCols;
		return *this;
	}

	//! \~english Resizes the 2D array to new dimensions.
	//! \~russian Изменяет размер двумерного массива.
	//! \details
	//! \~english If the new dimensions are larger, new elements are appended and filled with copies of `f`.
	//! If they are smaller, the array is truncated (excess elements are destroyed). The underlying memory may be reallocated.
	//! \~russian Если новые размеры больше текущих, новые элементы добавляются в конец и заполняются копиями `f`.
	//! Если новые размеры меньше, массив усекается (лишние элементы уничтожаются). Внутренняя память может быть перераспределена.
	//! \code
	//! PIVector2D<int> mat(2, 3, 0); // 2x3 matrix filled with 0
	//! mat.resize(3, 4, 1);          // becomes 3x4, new elements filled with 1
	//! \endcode
	//! \~\sa PIVector::resize()
	inline PIVector2D<T> & resize(size_t rows, size_t cols, const T & f = T()) {
		if (rows == rows_ && cols == cols_) return *this;
		if (rows_ == 0 || cols_ == 0) {
			mat.resize(rows * cols, f);
			rows_ = rows;
			cols_ = cols;
			return *this;
		}
		if (rows != rows_ && cols == cols_) {
			mat.resize(rows * cols_, f);
			rows_ = rows;
			return *this;
		}
		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.
	//! \~russian Оператор равенства.
	//! \~\sa PIVector::operator==
	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>>).
	//! \details
	//! \~english Each row vector is a copy of the corresponding row.
	//! \~russian Каждый вектор-строка является копией соответствующей строки.
	//! \~\sa fromVectors(), PIVector::PIVector(const T*, size_t)
	inline PIVector<PIVector<T>> toVectors() const {
		PIVector<PIVector<T>> ret;
		ret.reserve(rows_);
		for (size_t i = 0; i < rows_; ++i)
			ret << PIVector<T>(mat.data(i * cols_), cols_);
		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; }

	//! \~english Swaps this 2D array with another.
	//! \~russian Меняет местами этот двумерный массив с другим.
	//! \details
	//! \~english Swaps the flat vectors and the dimension members. Very fast, no memory allocation.
	//! \~russian Обменивает внутренние плоские векторы и члены, хранящие размеры. Очень быстро, без выделения памяти.
	//! \~\sa PIVector::swap()
	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;
		cols_ = c;
		mat._resizeRaw(r * c);
		return *this;
	}

	//! \~english Clears the array, removing all elements and setting dimensions to 0.
	//! \~russian Очищает массив, удаляя все элементы и устанавливая размеры в 0.
	//! \details
	//! \~english The capacity of the underlying flat vector may remain unchanged.
	//! \~russian Ёмкость внутреннего плоского вектора может остаться неизменной.
	//! \~\sa PIVector::clear()
	inline void clear() {
		rows_ = cols_ = 0;
		mat.clear();
	}


	//! \~english Checks if the underlying flat vector contains the element `e`.
	//! \~russian Проверяет, содержит ли внутренний плоский вектор элемент `e`.
	//! \~\sa PIVector::contains()
	inline bool contains(const T & e) const { return mat.contains(e); }

	//! \~english Counts occurrences of `e` in the underlying flat vector.
	//! \~russian Подсчитывает количество вхождений `e` во внутреннем плоском векторе.
	//! \~\sa PIVector::entries()
	inline int entries(const T & e) const { return mat.entries(e); }

	//! \~english Counts elements in the flat vector that pass the `test`.
	//! \~russian Подсчитывает элементы в плоском векторе, проходящие `test`.
	//! \~\sa PIVector::entries(std::function)
	inline int entries(std::function<bool(const T & e)> test) const { return mat.entries(std::move(test)); }


	//! \~english Returns the first index (row, col) of `e` in the 2D array.
	//! \~russian Возвращает первый индекс (строка, столбец) элемента `e` в двумерном массиве.
	//! \~\sa PIVector::indexOf()
	inline Index indexOf(const T & e) const {
		ssize_t flat = mat.indexOf(e);
		if (flat < 0 || cols_ == 0) return Index{-1, -1};
		return Index{flat / static_cast<ssize_t>(cols_), flat % static_cast<ssize_t>(cols_)};
	}

	//! \~english Returns the first index (row, col) in the 2D array that passes the `test`.
	//! \~russian Возвращает первый индекс (строка, столбец) в двумерном массиве, проходящий `test`.
	//! \~\sa PIVector::indexWhere()
	inline Index indexWhere(std::function<bool(const T & e)> test, ssize_t start = 0) const {
		ssize_t flat = mat.indexWhere(std::move(test), start);
		if (flat < 0 || cols_ == 0) return Index{-1, -1};
		return Index{flat / static_cast<ssize_t>(cols_), flat % static_cast<ssize_t>(cols_)};
	}

	//! \~english Returns the last index (row, col) of `e` in the 2D array.
	//! \~russian Возвращает последний индекс (строка, столбец) элемента `e` в двумерном массиве.
	//! \~\sa PIVector::lastIndexOf()
	inline Index lastIndexOf(const T & e, ssize_t start = -1) const {
		ssize_t flat = mat.lastIndexOf(e, start);
		if (flat < 0 || cols_ == 0) return Index{-1, -1};
		return Index{flat / static_cast<ssize_t>(cols_), flat % static_cast<ssize_t>(cols_)};
	}

	//! \~english Returns the last index (row, col) in the 2D array that passes the `test`.
	//! \~russian Возвращает последний индекс (строка, столбец) в двумерном массиве, проходящий `test`.
	//! \~\sa PIVector::lastIndexWhere()
	inline Index lastIndexWhere(std::function<bool(const T & e)> test, ssize_t start = -1) const {
		ssize_t flat = mat.lastIndexWhere(std::move(test), start);
		if (flat < 0 || cols_ == 0) return Index{-1, -1};
		return Index{flat / static_cast<ssize_t>(cols_), flat % static_cast<ssize_t>(cols_)};
	}


	//! \~english Tests if any element in the flat vector passes the `test`.
	//! \~russian Проверяет, проходит ли какой-либо элемент в плоском векторе `test`.
	//! \~\sa PIVector::any()
	inline bool any(std::function<bool(const T & e)> test) const { return mat.any(std::move(test)); }

	//! \~english Tests if all elements in the flat vector pass the `test`.
	//! \~russian Проверяет, проходят ли все элементы в плоском векторе `test`.
	//! \~\sa PIVector::every()
	inline bool every(std::function<bool(const T & e)> test) const { return mat.every(std::move(test)); }

	//! \~english Fills the entire 2D array with copies of `e`.
	//! \~russian Заполняет весь двумерный массив копиями `e`.
	//! \~\sa PIVector::fill()
	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` на основе плоского индекса.
	//! \~\sa PIVector::fill(std::function)
	inline PIVector2D<T> & fill(std::function<T(size_t i)> f) {
		mat.fill(std::move(f));
		return *this;
	}

	//! \~english Same as \a fill().
	//! \~russian То же, что и \a fill().
	inline PIVector2D<T> & assign(const T & e = T()) { return fill(e); }

	//! \~english Assigns new size and fills with value.
	//! \~russian Задаёт новый размер и заполняет значением.
	//! \~\sa PIVector::assign(size_t, const T&)
	inline PIVector2D<T> & assign(size_t rows, size_t cols, const T & f = T()) {
		mat.assign(rows * cols, f);
		rows_ = rows;
		cols_ = cols;
		return *this;
	}


	//! \~english Returns a transposed 2D array (rows become columns and vice versa).
	//! \~russian Возвращает транспонированный двумерный массив (строки становятся столбцами и наоборот).
	//! \details
	//! \~english The element at (r, c) in the original becomes at (c, r) in the result.
	//! \~russian Элемент (r, c) исходного массива становится элементом (c, r) в результате.
	//! \code
	//! PIVector2D<int> mat(2, 3, ...);
	//! auto t = mat.transposed(); // now 3x2
	//! \endcode
	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;
	}

	//! \~english Reverses the order of rows in place.
	//! \~russian Изменяет порядок строк на обратный на месте.
	//! \~\sa reverseColumns(), PIVector::reverse()
	inline PIVector2D<T> & reverseRows() {
		const size_t half = rows_ / 2;
		for (size_t i = 0; i < half; ++i) {
			T * row1 = data(i * cols_);
			T * row2 = data((rows_ - 1 - i) * cols_);
			for (size_t j = 0; j < cols_; ++j) {
				piSwap(row1[j], row2[j]);
			}
		}
		return *this;
	}

	//! \~english Reverses the order of columns in each row in place.
	//! \~russian Изменяет порядок столбцов в каждой строке на обратный на месте.
	//! \~\sa reverseRows(), PIVector::reverse()
	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 Возвращает подмассив (диапазон строк и столбцов).
	//! \details
	//! \~english If the range exceeds the array boundaries, it is clipped. If rowCount or colCount is 0, an empty array is returned.
	//! \~russian Если диапазон выходит за границы массива, он обрезается. Если rowCount или colCount равны 0, возвращается пустой массив.
	//! \~\sa PIVector::getRange()
	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>();
		const size_t actualRowCount = piMin<size_t>(rowCount, rows_ - rowStart);
		const size_t actualColCount = piMin<size_t>(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 (read-only).
	//! \~russian Применяет функцию к каждому элементу (только чтение).
	//! \details
	//! \~english The function can't modify the elements.
	//! \~russian Функция не может изменять элементы.
	//! \~\sa forEach (modifiable), PIVector::forEach()
	inline void forEach(std::function<void(const T &)> func) const { mat.forEach(func); }

	//! \~english Applies a function to each element (modifiable).
	//! \~russian Применяет функцию к каждому элементу (с возможностью изменения).
	//! \details
	//! \~english The function can modify the elements.
	//! \~russian Функция может изменять элементы.
	//! \~\sa forEach (read-only), PIVector::forEach()
	inline PIVector2D<T> & forEach(std::function<void(T &)> func) { mat.forEach(func); return *this; }

	//! \~english Applies a function to each element and returns a new 2D array of a different type.
	//! \~russian Применяет функцию к каждому элементу и возвращает новый двумерный массив другого типа.
	//! \details
	//! \~english The original array is not modified.
	//! \~russian Исходный массив не изменяется.
	//! \~\sa PIVector::map()
	template<typename ST>
	inline PIVector2D<ST> map(std::function<ST(const T & e)> f) const {
		return PIVector2D<ST>(rows_, cols_, mat.template map<ST>(std::move(f)));
	}

	//! \~english Applies a function (with row and col indices) to each element and returns a new 2D array.
	//! \~russian Применяет функцию (с индексами строки и столбца) к каждому элементу и возвращает новый двумерный массив.
	//! \~\sa PIVector::mapIndexed()
	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 Applies a function to each row (modifiable).
	//! \~russian Применяет функцию к каждой строке (с возможностью изменения).
	//! \~\sa forEachRow() const, PIVector::forEach()
	inline PIVector2D<T> & forEachRow(std::function<void(Row)> f) {
		for (size_t r = 0; r < rows_; ++r) {
			f(row(r));
		}
		return *this;
	}

	//! \~english Applies a function to each row (read-only).
	//! \~russian Применяет функцию к каждой строке (только чтение).
	inline void forEachRow(std::function<void(RowConst)> f) const {
		for (size_t r = 0; r < rows_; ++r) {
			f(row(r));
		}
	}

	//! \~english Applies a function to each column (modifiable).
	//! \~russian Применяет функцию к каждому столбцу (с возможностью изменения).
	inline PIVector2D<T> & forEachColumn(std::function<void(Col)> f) {
		for (size_t c = 0; c < cols_; ++c) {
			f(col(c));
		}
		return *this;
	}

	//! \~english Applies a function to each column (read-only).
	//! \~russian Применяет функцию к каждому столбцу (только чтение).
	//! \param f Function taking a \a ColConst.
	inline void forEachColumn(std::function<void(ColConst)> f) const {
		for (size_t c = 0; c < cols_; ++c) {
			f(col(c));
		}
	}

	//! \~english Accumulates a value across all elements.
	//! \~russian Аккумулирует значение по всем элементам.
	//! \~\sa PIVector::reduce()
	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>(std::move(f), initial);
	}

	//! \~english Accumulates a value across all elements with indices.
	//! \~russian Аккумулирует значение по всем элементам с индексами.
	//! \~\sa PIVector::reduceIndexed()
	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 Удаляет строку из двумерного массива.
	//! \details
	//! \~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<T> & removeRow(size_t row) { return deleteRows(row, 1); }

	//! \~english Removes a column from the 2D array.
	//! \~russian Удаляет столбец из двумерного массива.
	//! \details
	//! \~english This operation is more expensive than removing a row because elements must be moved.
	//! \~russian Эта операция дороже, чем удаление строки, поскольку требуется перемещение элементов.
	//! \~\sa removeRow(), PIVector::remove()
	inline PIVector2D<T> & removeColumn(size_t col) { return deleteColumns(col, 1); }

	//! \~english Removes all rows that satisfy a condition.
	//! \~russian Удаляет все строки, удовлетворяющие условию.
	//! \details
	//! \~english Rows are removed from the bottom to avoid index shifting issues.
	//! \~russian Строки удаляются снизу вверх, чтобы избежать проблем со смещением индексов.
	//! \~\sa removeColumnsWhere(), PIVector::removeWhere()
	inline PIVector2D<T> & removeRowsWhere(std::function<bool(const RowConst &)> test) {
		ssize_t r = rows_;
		while (--r >= 0) {
			if (test(RowConst(this, r))) {
				removeRow(r);
			}
		}
		return *this;
	}

	//! \~english Removes all columns that satisfy a condition.
	//! \~russian Удаляет все столбцы, удовлетворяющие условию.
	//! \~\sa removeRowsWhere()
	inline PIVector2D<T> & removeColumnsWhere(std::function<bool(const ColConst &)> test) {
		ssize_t c = cols_;
		while (--c >= 0) {
			if (test(ColConst(this, c))) {
				removeColumn(c);
			}
		}
		return *this;
	}


	//! \~english Returns a new 2D array containing only the rows that pass the test.
	//! \~russian Возвращает новый двумерный массив, содержащий только строки, прошедшие проверку.
	//! \~\sa filterColumns(), PIVector::filter()
	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 Возвращает новый двумерный массив, содержащий только столбцы, прошедшие проверку.
	//! \~\sa filterRows()
	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;
		for (size_t gc = 0; gc < goodCols.size(); ++gc) {
			result.addColumn(col(goodCols[gc]));
		}
		return result;
	}

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);
	s << "{";
	for (size_t i = 0; i < v.rows(); ++i) {
		s << "{ ";
		for (size_t j = 0; j < v.cols(); ++j) {
			s << v[i][j];
			if (j < v.cols() - 1) s << ", ";
		}
		s << " }";
		if (i < v.rows() - 1) s << PICoutManipulators::NewLine;
	}
	if (v.isEmpty()) s << "{ }";
	s << "}";
	s.restoreControls();
	return s;
}

//! \}

#endif // PIVECTOR2D_H
