diff --git a/.gitignore b/.gitignore index b883f1f..31decfc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +*.swp *.exe diff --git a/README.md b/README.md index b99ff10..90b2a6e 100644 --- a/README.md +++ b/README.md @@ -142,9 +142,18 @@ Make sure your type implements: * product `operator *` * output `operator <<` to be able to nicely output the matrix -You can see a simple custom element example in [the tests](tests/cases/custom-type.h). +You can see a simple custom element example in [the tests](tests/tests/custom-type.h). ----------- +### Roadmap + +In the nearest future following features are planned to be added: + +* `operator[]` for both getting and setting matrix values +* moving under a namespace to prevent collisions +* optimizing M-M multiplication and addition +* more matrix operations (transpose, scalar-matrix multiplication, matrix-scalar division, etc.) + Any questions / issues / pull requests / [donations](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=KWQJ7VTXZMZLS) are very welcome! :-) diff --git a/src/SparseMatrix/SparseMatrix.cpp b/src/SparseMatrix/SparseMatrix.cpp index b3d6e00..fb456e6 100644 --- a/src/SparseMatrix/SparseMatrix.cpp +++ b/src/SparseMatrix/SparseMatrix.cpp @@ -19,82 +19,82 @@ using namespace std; template SparseMatrix::SparseMatrix(int n) { - this->construct(n, n); + this->construct(n, n); } template SparseMatrix::SparseMatrix(int rows, int columns) { - this->construct(rows, columns); + this->construct(rows, columns); } template SparseMatrix::SparseMatrix(const SparseMatrix & matrix) { - this->deepCopy(matrix); + this->deepCopy(matrix); } template SparseMatrix & SparseMatrix::operator = (const SparseMatrix & matrix) { - if (&matrix != this) { - this->destruct(); - this->deepCopy(matrix); - } + if (&matrix != this) { + this->destruct(); + this->deepCopy(matrix); + } - return *this; + return *this; } template void SparseMatrix::deepCopy(const SparseMatrix & matrix) { - this->m = matrix.m; - this->n = matrix.n; - this->rows = new vector(*(matrix.rows)); - - if (matrix.vals != NULL) { - this->cols = new vector(*(matrix.cols)); - this->vals = new vector(*(matrix.vals)); - } + this->m = matrix.m; + this->n = matrix.n; + this->rows = new vector(*(matrix.rows)); + + if (matrix.vals != NULL) { + this->cols = new vector(*(matrix.cols)); + this->vals = new vector(*(matrix.vals)); + } } template SparseMatrix::~SparseMatrix(void) { - this->destruct(); + this->destruct(); } template void SparseMatrix::construct(int rows, int columns) { - if (rows < 1 || columns < 1) { - throw InvalidDimensionsException("Matrix dimensions cannot be zero or negative."); - } + if (rows < 1 || columns < 1) { + throw InvalidDimensionsException("Matrix dimensions cannot be zero or negative."); + } - this->m = rows; - this->n = columns; + this->m = rows; + this->n = columns; - this->vals = NULL; - this->cols = NULL; - this->rows = new vector(rows + 1, 1); + this->vals = NULL; + this->cols = NULL; + this->rows = new vector(rows + 1, 1); } template void SparseMatrix::destruct(void) { - if (this->vals != NULL) { - delete this->vals; - delete this->cols; - } + if (this->vals != NULL) { + delete this->vals; + delete this->cols; + } - delete this->rows; + delete this->rows; } @@ -103,14 +103,14 @@ void SparseMatrix::destruct(void) template int SparseMatrix::getRowCount(void) const { - return this->m; + return this->m; } template int SparseMatrix::getColumnCount(void) const { - return this->n; + return this->n; } @@ -119,54 +119,131 @@ int SparseMatrix::getColumnCount(void) const template T SparseMatrix::get(int row, int col) const { - this->validateCoordinates(row, col); + this->validateCoordinates(row, col); - int currCol; + int currCol; - for (int pos = (*(this->rows))[row - 1] - 1; pos < (*(this->rows))[row] - 1; ++pos) { - currCol = (*(this->cols))[pos]; + for (int pos = this->rows->at(row - 1) - 1; pos < this->rows->at(row) - 1; pos++) + { + currCol = this->cols->at(pos); - if (currCol == col) { - return (*(this->vals))[pos]; + if (currCol == col) { + return this->vals->at(pos); - } else if (currCol > col) { - break; - } - } + } else if (currCol > col) { + break; + } + } - return T(); + return T(); } +template +bool SparseMatrix::removeAnyEdge(int row, int& col) +{ + for (col = 1; col <= this->n; ++col) + { + this->validateCoordinates(row, col); + + int pos = this->rows->at(row - 1) - 1; + int currCol = 0; + + for (; pos < this->rows->at(row) - 1; pos++) + { + currCol = this->cols->at(pos); + if (currCol >= col) + break; + } + if (currCol == col) + { + this->remove(pos, row); + + return true; + } + } + return false; +} template -SparseMatrix & SparseMatrix::set(T val, int row, int col) +bool SparseMatrix::removeEdge(int row, int col) { - this->validateCoordinates(row, col); + this->validateCoordinates(row, col); + + int currCol; + + for (int pos = this->rows->at(row - 1) - 1; pos < this->rows->at(row) - 1; pos++) + { + currCol = this->cols->at(pos); + + if (currCol == col) + { + remove(pos, row); + return true; + } + else if (currCol > col) + { + break; + } + } + + return false; +} - int pos = (*(this->rows))[row - 1] - 1; - int currCol = 0; +template +vector SparseMatrix::getNeighbors(int row) +{ + vector neighbors; + for (int col = 1; col <= this->n; ++col) - for (; pos < (*(this->rows))[row] - 1; pos++) { - currCol = (*(this->cols))[pos]; + { + this->validateCoordinates(row, col); - if (currCol >= col) { - break; - } - } + int currCol; - if (currCol != col) { - if (!(val == T())) { - this->insert(pos, row, col, val); - } - } else if (val == T()) { - this->remove(pos, row); + for (int pos = this->rows->at(row - 1) - 1; pos < this->rows->at(row) - 1; pos++) + { + currCol = this->cols->at(pos); - } else { - (*(this->vals))[pos] = val; - } + if (currCol == col) + neighbors.push_back(this->vals->at(pos)); + else if (currCol > col) + break; + } + } - return *this; + return neighbors; +} + +template +SparseMatrix & SparseMatrix::set(int row, int col, T val) +{ + this->validateCoordinates(row, col); + + int pos = this->rows->at(row - 1) - 1; + int currCol = 0; + + for (; pos < this->rows->at(row) - 1; pos++) { + currCol = this->cols->at(pos); + + if (currCol >= col) { + break; + } + } + + if (currCol != col) { + if (!(val == T())) { + this->insert(pos, row, col, val); + } + + } else if (val == T()) { + this->remove(pos, row); + + } else { + this->vals->at(pos) = val; + } + + return *this; } @@ -175,167 +252,177 @@ SparseMatrix & SparseMatrix::set(T val, int row, int col) template vector SparseMatrix::multiply(const vector & x) const { - if (this->n != (int) x.size()) { - throw InvalidDimensionsException("Cannot multiply: Matrix column count and vector size don't match."); - } + if (this->n != (int) x.size()) { + throw InvalidDimensionsException("Cannot multiply: Matrix column count and vector size don't match."); + } - vector result(this->m, T()); + vector result(this->m, T()); - if (this->vals != NULL) { // only if any value set - for (int i = 0; i < this->m; i++) { - T sum = T(); - for (int j = (*(this->rows))[i]; j < (*(this->rows))[i + 1]; j++) { - sum = sum + (*(this->vals))[j - 1] * x[(*(this->cols))[j - 1] - 1]; - } + if (this->vals != NULL) { // only if any value set + for (int i = 0; i < this->m; i++) { + T sum = T(); + for (int j = this->rows->at(i); j < this->rows->at(i + 1); j++) { + sum = sum + this->vals->at(j - 1) * x[this->cols->at(j - 1) - 1]; + } - result[i] = sum; - } - } + result[i] = sum; + } + } - return result; + return result; } template vector SparseMatrix::operator * (const vector & x) const { - return this->multiply(x); + return this->multiply(x); } template SparseMatrix SparseMatrix::multiply(const SparseMatrix & m) const { - if (this->n != m.m) { - throw InvalidDimensionsException("Cannot multiply: Left matrix column count and right matrix row count don't match."); - } + if (this->n != m.m) { + throw InvalidDimensionsException("Cannot multiply: Left matrix column count and right matrix row count don't match."); + } - SparseMatrix result(this->m, m.n); + SparseMatrix result(this->m, m.n); - T a; + T a; - // TODO: more efficient? - // @see http://www.math.tamu.edu/~srobertp/Courses/Math639_2014_Sp/CRSDescription/CRSStuff.pdf + // TODO: more efficient? + // @see http://www.math.tamu.edu/~srobertp/Courses/Math639_2014_Sp/CRSDescription/CRSStuff.pdf - for (int i = 1; i <= this->m; i++) { - for (int j = 1; j <= m.n; j++) { - a = T(); + for (int i = 1; i <= this->m; i++) { + for (int j = 1; j <= m.n; j++) { + a = T(); - for (int k = 1; k <= this->n; k++) { - a = a + this->get(i, k) * m.get(k, j); - } + for (int k = 1; k <= this->n; k++) { + a = a + this->get(i, k) * m.get(k, j); + } - result.set(a, i, j); - } - } + result.set(a, i, j); + } + } - return result; + return result; } template SparseMatrix SparseMatrix::operator * (const SparseMatrix & m) const { - return this->multiply(m); + return this->multiply(m); } template SparseMatrix SparseMatrix::add(const SparseMatrix & m) const { - if (this->m != m.m || this->n != m.n) { - throw InvalidDimensionsException("Cannot add: matrices dimensions don't match."); - } + if (this->m != m.m || this->n != m.n) { + throw InvalidDimensionsException("Cannot add: matrices dimensions don't match."); + } - SparseMatrix result(this->m, this->n); + SparseMatrix result(this->m, this->n); - // TODO: more efficient? - // @see http://www.math.tamu.edu/~srobertp/Courses/Math639_2014_Sp/CRSDescription/CRSStuff.pdf + // TODO: more efficient? + // @see http://www.math.tamu.edu/~srobertp/Courses/Math639_2014_Sp/CRSDescription/CRSStuff.pdf - for (int i = 1; i <= this->m; i++) { - for (int j = 1; j <= this->n; j++) { - result.set(this->get(i, j) + m.get(i, j), i, j); - } - } + for (int i = 1; i <= this->m; i++) { + for (int j = 1; j <= this->n; j++) { + result.set(this->get(i, j) + m.get(i, j), i, j); + } + } - return result; + return result; } template SparseMatrix SparseMatrix::operator + (const SparseMatrix & m) const { - return this->add(m); + return this->add(m); } template SparseMatrix SparseMatrix::subtract(const SparseMatrix & m) const { - if (this->m != m.m || this->n != m.n) { - throw InvalidDimensionsException("Cannot subtract: matrices dimensions don't match."); - } + if (this->m != m.m || this->n != m.n) { + throw InvalidDimensionsException("Cannot subtract: matrices dimensions don't match."); + } - SparseMatrix result(this->m, this->n); + SparseMatrix result(this->m, this->n); - // TODO: more efficient? - // @see http://www.math.tamu.edu/~srobertp/Courses/Math639_2014_Sp/CRSDescription/CRSStuff.pdf + // TODO: more efficient? + // @see http://www.math.tamu.edu/~srobertp/Courses/Math639_2014_Sp/CRSDescription/CRSStuff.pdf - for (int i = 1; i <= this->m; i++) { - for (int j = 1; j <= this->n; j++) { - result.set(this->get(i, j) - m.get(i, j), i, j); - } - } + for (int i = 1; i <= this->m; i++) { + for (int j = 1; j <= this->n; j++) { + result.set(this->get(i, j) - m.get(i, j), i, j); + } + } - return result; + return result; } template SparseMatrix SparseMatrix::operator - (const SparseMatrix & m) const { - return this->subtract(m); + return this->subtract(m); } +template +int SparseMatrix::numberOfRowElement(int row) const +{ + validateCoordinates(row, 1); + return this->rows->at(row) - this->rows->at(row - 1); + +} + + + // === HELPERS / VALIDATORS ============================================== template void SparseMatrix::validateCoordinates(int row, int col) const { - if (row < 1 || col < 1 || row > this->m || col > this->n) { - throw InvalidCoordinatesException("Coordinates out of range."); - } + if (row < 1 || col < 1 || row > this->m || col > this->n) { + throw InvalidCoordinatesException("Coordinates out of range."); + } } template void SparseMatrix::insert(int index, int row, int col, T val) { - if (this->vals == NULL) { - this->vals = new vector(1, val); - this->cols = new vector(1, col); - - } else { - this->vals->insert(this->vals->begin() + index, val); - this->cols->insert(this->cols->begin() + index, col); - } - - for (int i = row; i <= this->m; i++) { - (*(this->rows))[i] += 1; - } + if (this->vals == NULL) { + this->vals = new vector(1, val); + this->cols = new vector(1, col); + + } else { + this->vals->insert(this->vals->begin() + index, val); + this->cols->insert(this->cols->begin() + index, col); + } + + for (int i = row; i <= this->m; i++) { + this->rows->at(i) = this->rows->at(i) + 1; + } } template void SparseMatrix::remove(int index, int row) { - this->vals->erase(this->vals->begin() + index); - this->cols->erase(this->cols->begin() + index); + this->vals->erase(this->vals->begin() + index); + this->cols->erase(this->cols->begin() + index); - for (int i = row; i <= this->m; i++) { - (*(this->rows))[i] -= 1; - } + for (int i = row; i <= this->m; i++) { + this->rows->at(i) = this->rows->at(i) - 1; + } } @@ -344,37 +431,37 @@ void SparseMatrix::remove(int index, int row) template bool operator == (const SparseMatrix & a, const SparseMatrix & b) { - return ((a.vals == NULL && b.vals == NULL) - || (a.vals != NULL && b.vals != NULL && *(a.vals) == *(b.vals))) - && ((a.cols == NULL && b.cols == NULL) - || (a.cols != NULL && b.cols != NULL && *(a.cols) == *(b.cols))) - && *(a.rows) == *(b.rows); + return ((a.vals == NULL && b.vals == NULL) + || (a.vals != NULL && b.vals != NULL && *(a.vals) == *(b.vals))) + && ((a.cols == NULL && b.cols == NULL) + || (a.cols != NULL && b.cols != NULL && *(a.cols) == *(b.cols))) + && *(a.rows) == *(b.rows); } template bool operator != (const SparseMatrix & a, const SparseMatrix & b) { - return !(a == b); + return !(a == b); } template ostream & operator << (ostream & os, const SparseMatrix & matrix) { - for (int i = 1; i <= matrix.m; i++) { - for (int j = 1; j <= matrix.n; j++) { - if (j != 1) { - os << " "; - } + for (int i = 1; i <= matrix.m; i++) { + for (int j = 1; j <= matrix.n; j++) { + if (j != 1) { + os << " "; + } - os << matrix.get(i, j); - } + os << matrix.get(i, j); + } - if (i < matrix.m) { - os << endl; - } - } + if (i < matrix.m) { + os << endl; + } + } - return os; + return os; } diff --git a/src/SparseMatrix/SparseMatrix.h b/src/SparseMatrix/SparseMatrix.h index bce1769..6e06233 100644 --- a/src/SparseMatrix/SparseMatrix.h +++ b/src/SparseMatrix/SparseMatrix.h @@ -8,87 +8,97 @@ #ifndef __SPARSEMATRIX_H__ - #define __SPARSEMATRIX_H__ +#define __SPARSEMATRIX_H__ - #include - #include +#include +#include - using namespace std; +using namespace std; - template - class SparseMatrix - { +template +class SparseMatrix +{ - public: +public: - // === CREATION ============================================== + // === CREATION ============================================== - SparseMatrix(int n); // square matrix n×n - SparseMatrix(int rows, int columns); // general matrix + SparseMatrix(int n); // square matrix n×n + SparseMatrix(int rows, int columns); // general matrix - SparseMatrix(const SparseMatrix & m); // copy constructor - SparseMatrix & operator = (const SparseMatrix & m); + SparseMatrix(const SparseMatrix & m); // copy constructor + SparseMatrix & operator = (const SparseMatrix & m); - ~SparseMatrix(void); + ~SparseMatrix(void); - // === GETTERS / SETTERS ============================================== + // === GETTERS / SETTERS ============================================== - int getRowCount(void) const; - int getColumnCount(void) const; + int getRowCount(void) const; + int getColumnCount(void) const; - // === VALUES ============================================== + // === VALUES ============================================== - T get(int row, int col) const; - SparseMatrix & set(T val, int row, int col); + T get(int row, int col) const; + SparseMatrix & set(int row, int col, T val); - // === OPERATIONS ============================================== + // === OPERATIONS ============================================== - vector multiply(const vector & x) const; - vector operator * (const vector & x) const; + vector multiply(const vector & x) const; + vector operator * (const vector & x) const; - SparseMatrix multiply(const SparseMatrix & m) const; - SparseMatrix operator * (const SparseMatrix & m) const; + SparseMatrix multiply(const SparseMatrix & m) const; + SparseMatrix operator * (const SparseMatrix & m) const; - SparseMatrix add(const SparseMatrix & m) const; - SparseMatrix operator + (const SparseMatrix & m) const; + SparseMatrix add(const SparseMatrix & m) const; + SparseMatrix operator + (const SparseMatrix & m) const; - SparseMatrix subtract(const SparseMatrix & m) const; - SparseMatrix operator - (const SparseMatrix & m) const; + SparseMatrix subtract(const SparseMatrix & m) const; + SparseMatrix operator - (const SparseMatrix & m) const; - // === FRIEND FUNCTIONS ========================================= + // === FRIEND FUNCTIONS ========================================= - template - friend bool operator == (const SparseMatrix & a, const SparseMatrix & b); + template + friend bool operator == (const SparseMatrix & a, const SparseMatrix & b); - template - friend bool operator != (const SparseMatrix & a, const SparseMatrix & b); + template + friend bool operator != (const SparseMatrix & a, const SparseMatrix & b); - template - friend ostream & operator << (ostream & os, const SparseMatrix & matrix); + template + friend ostream & operator << (ostream & os, const SparseMatrix & matrix); + // === Number of row elements ========================================= - protected: + int numberOfRowElement(int row) const; - int m, n; - vector * vals; - vector * rows, * cols; + bool removeAnyEdge(int row, int& col); + bool removeEdge(int row, int col); - // === HELPERS / VALIDATORS ============================================== + vector getNeighbors(int row); +private: - void construct(int m, int n); - void destruct(void); - void deepCopy(const SparseMatrix & m); - void validateCoordinates(int row, int col) const; - void insert(int index, int row, int col, T val); - void remove(int index, int row); + int m, n; - }; + vector * vals; + vector * rows, * cols; + + + // === HELPERS / VALIDATORS ============================================== + + void construct(int m, int n); + void destruct(void); + void deepCopy(const SparseMatrix & m); + void validateCoordinates(int row, int col) const; + void insert(int index, int row, int col, T val); + void remove(int index, int row); + + +}; #endif diff --git a/src/SparseMatrix/exceptions.h b/src/SparseMatrix/exceptions.h index c05663a..378ed46 100644 --- a/src/SparseMatrix/exceptions.h +++ b/src/SparseMatrix/exceptions.h @@ -8,58 +8,58 @@ #ifndef __SPARSEMATRIX_EXCEPTIONS_H__ - #define __SPARSEMATRIX_EXCEPTIONS_H__ +#define __SPARSEMATRIX_EXCEPTIONS_H__ - #include +#include - using namespace std; +using namespace std; - class Exception : public exception - { +class Exception : public exception +{ - public: +public: - explicit Exception(const string & message) : exception(), message(message) - {} + explicit Exception(const string & message) : exception(), message(message) + {} - virtual ~Exception(void) throw () - {} + virtual ~Exception(void) throw () + {} - inline string getMessage(void) const - { - return this->message; - } + inline string getMessage(void) const + { + return this->message; + } - protected: +protected: - string message; + string message; - }; +}; - class InvalidDimensionsException : public Exception - { +class InvalidDimensionsException : public Exception +{ - public: +public: - InvalidDimensionsException(const string & message) : Exception(message) - {} + InvalidDimensionsException(const string & message) : Exception(message) + {} - }; +}; - class InvalidCoordinatesException : public Exception - { +class InvalidCoordinatesException : public Exception +{ - public: +public: - InvalidCoordinatesException(const string & message) : Exception(message) - {} + InvalidCoordinatesException(const string & message) : Exception(message) + {} - }; +}; -#endif \ No newline at end of file +#endif diff --git a/tests/SparseMatrixMock.h b/tests/SparseMatrixMock.h new file mode 100644 index 0000000..2db7a5c --- /dev/null +++ b/tests/SparseMatrixMock.h @@ -0,0 +1,152 @@ +/** + * This file is part of the SparseMatrix library + * + * @license MIT + * @author Petr Kessler (https://kesspess.cz) + * @link https://github.com/uestla/Sparse-Matrix + */ + +#ifndef __SPARSEMATRIX_MOCK_H__ + + #define __SPARSEMATRIX_MOCK_H__ + + #include + #include "../src/SparseMatrix/SparseMatrix.h" + + using namespace std; + + + /** + * This class is used only for testing purposes + * + * @internal + */ + template + class SparseMatrixMock : public SparseMatrix + { + + public: + + SparseMatrixMock(int n) : SparseMatrix(n) + {} + + + SparseMatrixMock(int rows, int columns) : SparseMatrix(rows, columns) + {} + + + /** @return Non-empty values in the matrix */ + vector * getValues(void) + { + return this->vals; + } + + + /** @return Column pointers */ + vector * getColumnPointers(void) + { + return this->cols; + } + + + /** @return Row pointers */ + vector * getRowPointers(void) + { + return this->rows; + } + + + /** + * Sends internal storage info to given output stream + * + * @param os output stream + * @return void + */ + void printInfo(ostream & os) const + { + vector::iterator intIt; + + os << "rows (" << this->rows->size() << "): ["; + + for (intIt = this->rows->begin(); intIt < this->rows->end(); intIt++) { + if (intIt > this->rows->begin()) { + os << ", "; + } + + os << *intIt; + } + + os << "]"; + + os << endl << "cols"; + if (this->cols == NULL) { + os << ": NULL"; + + } else { + os << " (" << this->cols->size() << "): ["; + + for (intIt = this->cols->begin(); intIt < this->cols->end(); intIt++) { + if (intIt > this->cols->begin()) { + os << ", "; + } + + os << *intIt; + } + + os << "]"; + } + + os << endl << "vals"; + if (this->vals == NULL) { + os << ": NULL"; + + } else { + typename vector::iterator valIt; + + os << " (" << this->vals->size() << "): ["; + + for (valIt = this->vals->begin(); valIt < this->vals->end(); valIt++) { + if (valIt > this->vals->begin()) { + os << ", "; + } + + os << *valIt; + } + + os << "]"; + } + } + + + /** @return Constructed SparseMatrix */ + static SparseMatrixMock fromVectors(vector > vec) + { + SparseMatrixMock matrix(vec.size(), vec[0].size()); + + for (int i = 0, len = vec.size(); i < len; i++) { + for (int j = 0, len = vec[i].size(); j < len; j++) { + matrix.set(vec[i][j], i + 1, j + 1); + } + } + + return matrix; + } + + }; + + + template + bool operator == (const SparseMatrix & sparse, const vector > & classical) + { + for (int i = 0, rows = classical.size(); i < rows; i++) { + for (int j = 0, cols = classical[i].size(); j < cols; j++) { + if (sparse.get(i + 1, j + 1) != classical[i][j]) { + return false; + } + } + } + + return true; + } + +#endif diff --git a/tests/cases/subtraction.h b/tests/cases/subtraction.h index c3ed5f6..f789690 100644 --- a/tests/cases/subtraction.h +++ b/tests/cases/subtraction.h @@ -71,7 +71,13 @@ void testSubtraction(void) SparseMatrixMock sparseMatrixB = SparseMatrixMock::fromVectors(classicMatrixB); // calculate result manually - vector > manualResult = subtractMatrices(classicMatrixA, classicMatrixB); + vector > manualResult(rows, vector(cols, 0)); + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + manualResult[i][j] += classicMatrixA[i][j] - classicMatrixB[i][j]; + } + } // method assertEquals, vector > >( diff --git a/tests/inc/helpers.h b/tests/inc/helpers.h index f7df40f..830f0b1 100644 --- a/tests/inc/helpers.h +++ b/tests/inc/helpers.h @@ -66,24 +66,6 @@ } - template - vector > subtractMatrices(const vector > & a, const vector > & b) - { - int rows = a.size(); - int cols = a.front().size(); - - vector > result(rows, vector(cols, 0)); - - for (int i = 0; i < rows; i++) { - for (int j = 0; j < cols; j++) { - result[i][j] = a[i][j] - b[i][j]; - } - } - - return result; - } - - template vector multiplyMatrixByVector(const vector > & m, const vector & v) { diff --git a/tests/run.cpp b/tests/run.cpp index 30a275c..979ec68 100644 --- a/tests/run.cpp +++ b/tests/run.cpp @@ -9,18 +9,17 @@ #include #include #include -#include "inc/testslib.h" -#include "inc/helpers.h" +#include "testslib.h" #include "../src/SparseMatrix/SparseMatrix.cpp" -#include "cases/constructor.h" -#include "cases/get-set.h" -#include "cases/multiplication.h" -#include "cases/addition.h" -#include "cases/subtraction.h" -#include "cases/crs-format.h" -#include "cases/output.h" -#include "cases/custom-type.h" +#include "tests/constructor.h" +#include "tests/get-set.h" +#include "tests/multiplication.h" +#include "tests/addition.h" +#include "tests/subtraction.h" +#include "tests/crs-format.h" +#include "tests/output.h" +#include "tests/custom-type.h" using namespace std; diff --git a/tests/tests/addition.h b/tests/tests/addition.h new file mode 100644 index 0000000..07b3c98 --- /dev/null +++ b/tests/tests/addition.h @@ -0,0 +1,98 @@ +/** + * This file is part of the SparseMatrix library + * + * @license MIT + * @author Petr Kessler (https://kesspess.cz) + * @link https://github.com/uestla/Sparse-Matrix + */ + +#include "../testslib.h" +#include "../SparseMatrixMock.h" + + +void _additionFail1(void) +{ + SparseMatrix a(3, 4), b(3, 5); + a.add(b); +} + + +void testAdditionFail1(void) +{ + cout << "add() fail #1..." << flush; + assertException("InvalidDimensionsException", _additionFail1); + cout << " OK" << endl; +} + + +void _additionFail2(void) +{ + SparseMatrix a(3, 4), b(4, 4); + a.add(b); +} + + +void testAdditionFail2(void) +{ + cout << "add() fail #2..." << flush; + assertException("InvalidDimensionsException", _additionFail2); + cout << " OK" << endl; +} + + +void _additionFail3(void) +{ + SparseMatrix a(3, 4), b(4, 5); + a.add(b); +} + + +void testAdditionFail3(void) +{ + cout << "add() fail #3..." << flush; + assertException("InvalidDimensionsException", _additionFail3); + cout << " OK" << endl; +} + + +void testAddition(void) +{ + for (int N = 0; N < 5e3; N++) { + cout << "\rmatrices addition... #" << N + 1 << flush; + + // generate random matrices + int rows = rand() % 16 + 1; + int cols = rand() % 16 + 1; + + vector > classicMatrixA = generateRandomMatrix(rows, cols); + SparseMatrixMock sparseMatrixA = SparseMatrixMock::fromVectors(classicMatrixA); + + vector > classicMatrixB = generateRandomMatrix(rows, cols); + SparseMatrixMock sparseMatrixB = SparseMatrixMock::fromVectors(classicMatrixB); + + // calculate result manually + vector > manualResult(rows, vector(cols, 0)); + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + manualResult[i][j] += classicMatrixA[i][j] + classicMatrixB[i][j]; + } + } + + // method + assertEquals, vector > >( + sparseMatrixA.add(sparseMatrixB), + manualResult, + "Incorrect matrices addition" + ); + + // operator + assertEquals, vector > >( + sparseMatrixA + sparseMatrixB, + manualResult, + "Incorrect matrices addition (operator +)" + ); + } + + cout << " OK" << endl; +} diff --git a/tests/tests/constructor.h b/tests/tests/constructor.h new file mode 100644 index 0000000..815c609 --- /dev/null +++ b/tests/tests/constructor.h @@ -0,0 +1,66 @@ +/** + * This file is part of the SparseMatrix library + * + * @license MIT + * @author Petr Kessler (https://kesspess.cz) + * @link https://github.com/uestla/Sparse-Matrix + */ + +#include "../testslib.h" +#include "../SparseMatrixMock.h" + + +void _constructorFail1(void) +{ + SparseMatrix(0); +} + + +void testConstructorFail1(void) +{ + cout << "constructor fail #1..." << flush; + assertException("InvalidDimensionsException", _constructorFail1); + cout << " OK" << endl; +} + + +void _constructorFail2(void) +{ + SparseMatrix(0, 1); +} + + +void testConstructorFail2(void) +{ + cout << "constructor fail #2..." << flush; + assertException("InvalidDimensionsException", _constructorFail2); + cout << " OK" << endl; +} + + +void _constructorFail3(void) +{ + SparseMatrix(1, 0); +} + + +void testConstructorFail3(void) +{ + cout << "constructor fail #3..." << flush; + assertException("InvalidDimensionsException", _constructorFail3); + cout << " OK" << endl; +} + + +void _constructorFail4(void) +{ + SparseMatrix(0, 0); +} + + +void testConstructorFail4(void) +{ + cout << "constructor fail #4..." << flush; + assertException("InvalidDimensionsException", _constructorFail4); + cout << " OK" << endl; +} diff --git a/tests/tests/crs-format.h b/tests/tests/crs-format.h new file mode 100644 index 0000000..17cdcb7 --- /dev/null +++ b/tests/tests/crs-format.h @@ -0,0 +1,163 @@ +/** + * This file is part of the SparseMatrix library + * + * @license MIT + * @author Petr Kessler (https://kesspess.cz) + * @link https://github.com/uestla/Sparse-Matrix + */ + +#include "../testslib.h" +#include "../SparseMatrixMock.h" + + +void testInternalStorage(void) +{ + cout << "internal storage..." << flush; + + /* + "Standard" matrix + [ 1 0 4 5 ] + [ 2 -1 0 0 ] + [ 0 0 3 2 ] + + should be stored as + rows: [ 1, 4, 6, 8 ] + columns: [ 1, 3, 4, 1, 2, 3, 4 ] + values: [ 1, 4, 5, 2, -1, 3, 2 ] + */ + + SparseMatrixMock m1(3, 4); + m1.set(1, 1, 1) + .set(4, 1, 3) + .set(5, 1, 4) + .set(2, 2, 1) + .set(-1, 2, 2) + .set(3, 3, 3) + .set(2, 3, 4); + + vector rowPointers1; + rowPointers1.push_back(1); + rowPointers1.push_back(4); + rowPointers1.push_back(6); + rowPointers1.push_back(8); + assertEquals >(rowPointers1, *(m1.getRowPointers()), "Incorrect internal row pointers"); + + vector columnPointers1; + columnPointers1.push_back(1); + columnPointers1.push_back(3); + columnPointers1.push_back(4); + columnPointers1.push_back(1); + columnPointers1.push_back(2); + columnPointers1.push_back(3); + columnPointers1.push_back(4); + assertEquals >(columnPointers1, *(m1.getColumnPointers()), "Incorrect internal column pointers"); + + vector values1; + values1.push_back(1); + values1.push_back(4); + values1.push_back(5); + values1.push_back(2); + values1.push_back(-1); + values1.push_back(3); + values1.push_back(2); + assertEquals >(values1, *(m1.getValues()), "Incorrect internal values storage"); + + + /* + Matrix with empty row + [ 10 0 0 2 ] + [ 0 0 0 0 ] + [ 3 1 0 4 ] + + should be stored as + rows: [ 1, 3, 3, 6 ] + columns: [ 1, 4, 1, 2, 4 ] + values: [ 10, 2, 3, 1, 4 ] + */ + + SparseMatrixMock m2(3, 4); + m2.set(10, 1, 1) + . set(2, 1, 4) + . set(3, 3, 1) + . set(1, 3, 2) + . set(4, 3, 4); + + vector rowPointers2; + rowPointers2.push_back(1); + rowPointers2.push_back(3); + rowPointers2.push_back(3); + rowPointers2.push_back(6); + assertEquals >(rowPointers2, *(m2.getRowPointers()), "Incorrect internal row pointers"); + + vector columnPointers2; + columnPointers2.push_back(1); + columnPointers2.push_back(4); + columnPointers2.push_back(1); + columnPointers2.push_back(2); + columnPointers2.push_back(4); + assertEquals >(columnPointers2, *(m2.getColumnPointers()), "Incorrect internal column pointers"); + + vector values2; + values2.push_back(10); + values2.push_back(2); + values2.push_back(3); + values2.push_back(1); + values2.push_back(4); + assertEquals >(values2, *(m2.getValues()), "Incorrect internal values storage"); + + + /* + Previous matrix after adding non-zero element to empty row + + should be stored as + rows: [ 1, 3, 4, 7 ] + columns: [ 1, 4, 2, 1, 2, 4 ] + values: [ 10, 2, 5, 3, 1, 4 ] + */ + + SparseMatrixMock m3 = m2; + m3.set(5, 2, 2); + + vector rowPointers3; + rowPointers3.push_back(1); + rowPointers3.push_back(3); + rowPointers3.push_back(4); + rowPointers3.push_back(7); + assertEquals >(rowPointers3, *(m3.getRowPointers()), "Incorrect internal row pointers"); + + vector columnPointers3; + columnPointers3.push_back(1); + columnPointers3.push_back(4); + columnPointers3.push_back(2); + columnPointers3.push_back(1); + columnPointers3.push_back(2); + columnPointers3.push_back(4); + assertEquals >(columnPointers3, *(m3.getColumnPointers()), "Incorrect internal column pointers"); + + vector values3; + values3.push_back(10); + values3.push_back(2); + values3.push_back(5); + values3.push_back(3); + values3.push_back(1); + values3.push_back(4); + assertEquals >(values3, *(m3.getValues()), "Incorrect internal values storage"); + + /* + Previous matrix with removed the only non-zero element on 2nd row (should be equal to 2nd matrix) + + should be stored as + rows: [ 1, 3, 3, 6 ] + columns: [ 1, 4, 1, 2, 4 ] + values: [ 10, 2, 3, 1, 4 ] + */ + + SparseMatrixMock m4 = m3; + m4.set(0, 2, 2); + + assertEquals >(rowPointers2, *(m4.getRowPointers()), "Incorrect internal row pointers"); + assertEquals >(columnPointers2, *(m4.getColumnPointers()), "Incorrect internal column pointers"); + assertEquals >(values2, *(m4.getValues()), "Incorrect internal values storage"); + + cout << " OK" << endl; +} diff --git a/tests/tests/custom-type.h b/tests/tests/custom-type.h new file mode 100644 index 0000000..6f5eae8 --- /dev/null +++ b/tests/tests/custom-type.h @@ -0,0 +1,144 @@ +/** + * This file is part of the SparseMatrix library + * + * @license MIT + * @author Petr Kessler (https://kesspess.cz) + * @link https://github.com/uestla/Sparse-Matrix + */ + +#include +#include +#include "../testslib.h" +#include "../SparseMatrixMock.h" + +using namespace std; + + +string joinStrings(const string & a, const string & b) +{ + string c = a; + + if (a.length() && b.length()) { + c += " "; + } + + c += b; + return c; +} + + +struct person +{ + + person(void) : firstname(""), lastname("") + {} + + + person(const person & p) : firstname(p.firstname), lastname(p.lastname) + {} + + + person(const string & f, const string & l) : firstname(f), lastname(l) + {} + + + bool operator == (const person & p) const + { + return this->firstname == p.firstname && this->lastname == p.lastname; + } + + + person operator + (const person & p) const + { + return person(joinStrings(this->firstname, p.firstname), joinStrings(this->lastname, p.lastname)); + } + + + person operator - (const person & p) const + { + return person(p.lastname, this->lastname); + } + + + person operator * (const person & p) const + { + if (*this == person() || p == person()) { + return person(); + } + + return person(joinStrings(this->firstname, p.lastname), joinStrings(this->lastname, p.firstname)); + } + + + string firstname, lastname; + +}; + + +ostream & operator << (ostream & os, const person & p) +{ + os << p.firstname << ", " << p.lastname; + return os; +} + + +void testElementTypes(void) +{ + cout << "custom element types..." << flush; + + // addition + + SparseMatrix a(4, 5); + a.set(person("John", "Doe"), 3, 2); + + SparseMatrix b(4, 5); + b.set(person("Foo", "Bar"), 3, 2); + + SparseMatrix sum = a.add(b); + assertEquals(person("John Foo", "Doe Bar"), sum.get(3, 2)); + + + // subtraction + + SparseMatrix diff = a.subtract(b); + assertEquals(person("Bar", "Doe"), diff.get(3, 2)); + + + // matrix-matrix multiplication + + SparseMatrix c(5, 3); + c.set(person("Foo", "Bar"), 2, 3); + + SparseMatrix product = a.multiply(c); + + for (int i = 1, rows = product.getRowCount(); i <= rows; i++) { + for (int j = 1, cols = product.getColumnCount(); j <= cols; j++) { + person value = product.get(i, j); + + if (i == 3 && j == 3) { + assertEquals(person("John Bar", "Doe Foo"), value); + + } else { + assertEquals(person(), value); + } + } + } + + + // vector-matrix multiplication + + vector people; + people.push_back(person("John", "Doe")); + people.push_back(person("Foo", "Bar")); + people.push_back(person("Willy", "Wonka")); + people.push_back(person("Jon", "Snow")); + people.push_back(person("Bridget", "Johnes")); + + vector result = a.multiply(people); + assertEquals(person(), result[0]); + assertEquals(person(), result[1]); + assertEquals(person("John Bar", "Doe Foo"), result[2]); + assertEquals(person(), result[3]); + + cout << " OK" << endl; +} diff --git a/tests/tests/get-set.h b/tests/tests/get-set.h new file mode 100644 index 0000000..3e04ebc --- /dev/null +++ b/tests/tests/get-set.h @@ -0,0 +1,58 @@ +/** + * This file is part of the SparseMatrix library + * + * @license MIT + * @author Petr Kessler (https://kesspess.cz) + * @link https://github.com/uestla/Sparse-Matrix + */ + +#include "../testslib.h" +#include "../SparseMatrixMock.h" + + +void _getFail(void) +{ + SparseMatrix m(1, 1); + m.get(2, 1); +} + + +void testGetFail(void) +{ + cout << "get() fail..." << flush; + assertException("InvalidCoordinatesException", _getFail); + cout << " OK" << endl; +} + + +void _setFail(void) +{ + SparseMatrix m(3, 4); + m.set(-1, 4, 0); +} + + +void testSetFail(void) +{ + cout << "set() fail..." << flush; + assertException("InvalidCoordinatesException", _setFail); + cout << " OK" << endl; +} + + +void testGettersAndSetters(void) +{ + cout << "getters/setters..." << flush; + + SparseMatrix m(3); + for (int i = 1; i <= 3; i++) { + for (int j = 1; j <= 3; j++) { + assertEquals(0, m.get(i, j)); + } + } + + m.set(-4, 1, 3); + assertEquals(-4, m.get(1, 3)); + + cout << " OK" << endl; +} diff --git a/tests/tests/multiplication.h b/tests/tests/multiplication.h new file mode 100644 index 0000000..45f9510 --- /dev/null +++ b/tests/tests/multiplication.h @@ -0,0 +1,123 @@ +/** + * This file is part of the SparseMatrix library + * + * @license MIT + * @author Petr Kessler (https://kesspess.cz) + * @link https://github.com/uestla/Sparse-Matrix + */ + +#include "../testslib.h" +#include "../SparseMatrixMock.h" + + +void _multiplicationFail1(void) +{ + SparseMatrix m(3, 4); + vector x(3, 1); + m.multiply(x); +} + + +void testMultiplicationFail1(void) +{ + cout << "multiply() fail #1..." << flush; + assertException("InvalidDimensionsException", _multiplicationFail1); + cout << " OK" << endl; +} + + +void _multiplicationFail2(void) +{ + SparseMatrix a(3, 4), b(5, 6); + a.multiply(b); +} + + +void testMultiplicationFail2(void) +{ + cout << "multiply() fail #2..." << flush; + assertException("InvalidDimensionsException", _multiplicationFail2); + cout << " OK" << endl; +} + + +void testVectorMultiplication(void) +{ + for (int N = 0; N < 5e3; N++) { + cout << "\rvector multiplication... #" << N + 1 << flush; + + // generate random vector and matrix + int rows = rand() % 16 + 1; + int cols = rand() % 16 + 1; + + vector vec = generateRandomVector(cols); + + vector > classicMatrix = generateRandomMatrix(rows, cols); + SparseMatrixMock sparseMatrix = SparseMatrixMock::fromVectors(classicMatrix); + + // calculate result manually + vector manualResult(rows, 0); + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + manualResult[i] += classicMatrix[i][j] * vec[j]; + } + } + + // method + assertEquals >(manualResult, sparseMatrix.multiply(vec), "Incorrect vector multiplication"); + + // operator + assertEquals >(manualResult, sparseMatrix * vec, "Incorrect vector multiplication (operator *)"); + } + + cout << " OK" << endl; +} + + +void testMatricesMultiplication(void) +{ + for (int N = 0; N < 5e3; N++) { + cout << "\rmatrices multiplication... #" << N + 1 << flush; + + // generate random matrices + int rowsA = rand() % 16 + 1; + int colsArowsB = rand() % 16 + 1; + int colsB = rand() % 16 + 1; + + vector > classicMatrixA = generateRandomMatrix(rowsA, colsArowsB); + SparseMatrixMock sparseMatrixA = SparseMatrixMock::fromVectors(classicMatrixA); + + vector > classicMatrixB = generateRandomMatrix(colsArowsB, colsB); + SparseMatrixMock sparseMatrixB = SparseMatrixMock::fromVectors(classicMatrixB); + + // calculate result manually + vector > manualResult(rowsA, vector(colsB, 0)); + + for (int i = 0; i < rowsA; i++) { + for (int j = 0; j < colsB; j++) { + manualResult[i][j] = 0; + + for (int k = 0; k < colsArowsB; k++) { // rows in B + manualResult[i][j] += classicMatrixA[i][k] * classicMatrixB[k][j]; + } + } + } + + // method + assertEquals, vector > >( + sparseMatrixA.multiply(sparseMatrixB), + manualResult, + "Incorrect matrices multiplication" + ); + + // operator + assertEquals, vector > >( + sparseMatrixA * sparseMatrixB, + manualResult, + "Incorrect matrices multiplication (operator *)" + ); + } + + cout << " OK" << endl; +} diff --git a/tests/tests/output.h b/tests/tests/output.h new file mode 100644 index 0000000..c941436 --- /dev/null +++ b/tests/tests/output.h @@ -0,0 +1,33 @@ +/** + * This file is part of the SparseMatrix library + * + * @license MIT + * @author Petr Kessler (https://kesspess.cz) + * @link https://github.com/uestla/Sparse-Matrix + */ + +#include "../testslib.h" +#include "../SparseMatrixMock.h" + + +void testOutput(void) +{ + cout << "output..." << flush; + + ostringstream oss; + string output; + + SparseMatrix m(3, 3); + oss << m; + assertEquals("0 0 0\n0 0 0\n0 0 0", oss.str()); + + m.set(7, 1, 3) + .set(5, 2, 2) + .set(3, 3, 1); + + oss.str(""); + oss << m; + assertEquals("0 0 7\n0 5 0\n3 0 0", oss.str()); + + cout << " OK" << endl; +} diff --git a/tests/tests/subtraction.h b/tests/tests/subtraction.h new file mode 100644 index 0000000..e4adf3e --- /dev/null +++ b/tests/tests/subtraction.h @@ -0,0 +1,98 @@ +/** + * This file is part of the SparseMatrix library + * + * @license MIT + * @author Petr Kessler (https://kesspess.cz) + * @link https://github.com/uestla/Sparse-Matrix + */ + +#include "../testslib.h" +#include "../SparseMatrixMock.h" + + +void _subtractionFail1(void) +{ + SparseMatrix a(3, 4), b(3, 5); + a.subtract(b); +} + + +void testSubtractionFail1(void) +{ + cout << "subtract() fail #1..." << flush; + assertException("InvalidDimensionsException", _subtractionFail1); + cout << " OK" << endl; +} + + +void _subtractionFail2(void) +{ + SparseMatrix a(3, 4), b(4, 4); + a.subtract(b); +} + + +void testSubtractionFail2(void) +{ + cout << "subtract() fail #2..." << flush; + assertException("InvalidDimensionsException", _subtractionFail2); + cout << " OK" << endl; +} + + +void _subtractionFail3(void) +{ + SparseMatrix a(3, 4), b(4, 5); + a.subtract(b); +} + + +void testSubtractionFail3(void) +{ + cout << "subtract() fail #3..." << flush; + assertException("InvalidDimensionsException", _subtractionFail3); + cout << " OK" << endl; +} + + +void testSubtraction(void) +{ + for (int N = 0; N < 5e3; N++) { + cout << "\rmatrices subtraction... #" << N + 1 << flush; + + // generate random matrices + int rows = rand() % 16 + 1; + int cols = rand() % 16 + 1; + + vector > classicMatrixA = generateRandomMatrix(rows, cols); + SparseMatrixMock sparseMatrixA = SparseMatrixMock::fromVectors(classicMatrixA); + + vector > classicMatrixB = generateRandomMatrix(rows, cols); + SparseMatrixMock sparseMatrixB = SparseMatrixMock::fromVectors(classicMatrixB); + + // calculate result manually + vector > manualResult(rows, vector(cols, 0)); + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + manualResult[i][j] += classicMatrixA[i][j] - classicMatrixB[i][j]; + } + } + + // method + assertEquals, vector > >( + sparseMatrixA.subtract(sparseMatrixB), + manualResult, + "Incorrect matrices subtract" + ); + + // operator + assertEquals, vector > >( + sparseMatrixA - sparseMatrixB, + manualResult, + "Incorrect matrices subtract (operator -)" + ); + } + + cout << " OK" << endl; +} diff --git a/tests/testslib.h b/tests/testslib.h new file mode 100644 index 0000000..d3bbd6e --- /dev/null +++ b/tests/testslib.h @@ -0,0 +1,156 @@ +/** + * This file is part of the SparseMatrix library + * + * @license MIT + * @author Petr Kessler (https://kesspess.cz) + * @link https://github.com/uestla/Sparse-Matrix + */ + +#ifndef __TESTSLIB_H__ + + #define __TESTSLIB_H__ + + #include + #include + #include + #include + #include + #include + #include + + using namespace std; + + + class FailureException : public exception + { + + public: + + FailureException(const string & message) : exception(), message(message) + {} + + + virtual ~FailureException(void) throw () + {} + + + inline string getMessage(void) const + { + return this->message; + } + + + protected: + + string message; + + }; + + + void assertException(const char * exceptionClass, void (*callback)(void)) + { + try { + callback(); + + } catch (const exception & e) { + string actualClass(typeid(e).name()); + + if (strstr(actualClass.c_str(), exceptionClass) == NULL) { + ostringstream oss; + oss << "Exception class '" << exceptionClass << "' expected, but '" << actualClass << "' thrown."; + + throw FailureException(oss.str()); + } + + return ; + } + + throw FailureException("Exception expected but none thrown."); + } + + + template + void assertEquals(const T & a, const T & b, const char * message = NULL) + { + if (!(a == b)) { + ostringstream oss; + if (message == NULL) { + oss << "Objects not equal when they should be." << endl; + + } else { + oss << message << endl; + } + + oss << a << endl << "expected, but" << endl << b << " given"; + throw FailureException(oss.str()); + } + } + + + template + void assertEquals(const X & a, const Y & b, const char * message = NULL) + { + if (!(a == b)) { + ostringstream oss; + if (message == NULL) { + oss << "Objects not equal when they should be." << endl; + + } else { + oss << message << endl; + } + + oss << a << endl << "expected, but" << endl << b << " given"; + throw FailureException(oss.str()); + } + } + + + template + vector generateRandomVector(int size) + { + vector vector(size, 0); + + for (int i = 0; i < size; i++) { + vector[i] = rand() % 101; + } + + return vector; + } + + + template + vector > generateRandomMatrix(int rows, int columns) + { + vector > matrix(rows, vector(columns, 0)); + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < columns; j++) { + matrix[i][j] = rand() % 101; + } + } + + return matrix; + } + + + // === OUTPUT HELPERS ========================================= + + template + ostream & operator << (ostream & os, const vector & v) + { + os << "["; + + for (int i = 0, len = v.size(); i < len; i++) { + if (i != 0) { + os << ", "; + } + + os << v[i]; + } + + os << "]"; + + return os; + } + +#endif