package de.skuzzle.polly.tools.math;
import java.util.Arrays;
import de.skuzzle.polly.tools.strings.StringUtils;
/**
* <p>This class represents a rectangular matrix of size m times n, where m denotes the
* number of rows and n the number of columns. When accessing single elements of a matrix
* or iterating over it, we always use {@code i} to denote a row number and {@code j} to
* denote column numbers.</p>
*
* <p>Please note that row and column indexing starts at zero and thus the maximum row
* index is {@code matrix.getM() - 1} and the maximum column index is
* {@code matrix.getN() - 1}</p>
*
* <p>As in linear algebra, a matrix can be created over any algebraic {@link Field}. This
* abstract implementation allows full control of how the elements in the matrix are
* being summed or multiplied. For example one could create a matrix which uses the
* residue class of modulo 5 and all matrix operations will automatically conform to
* those operation in that field. The class {@link Fields} contains several useful
* default implementations of the Field interface.</p>
*
* <p>This class provides default matrix operations that are multiplication by a scalar,
* matrix multiplication (not commutative!) and adding two matrices. Then there are some
* methods for elementary row operations as swapping rows, multiplying a single row with
* a scalar and adding the multiple of one row to another.</p>
*
* @author Simon
*
* @param <K> Type for the elements in this matrix.
*/
public class Matrix<K> {
/**
* This exception is thrown whenever a matrix operations being performed on matrices
* with incompatible dimensions. For example, two matrices can only be multiplied if
* the column count of the left matrix equals the row count of the right one.
*
* @author Simon
*/
public static class DimensionException extends RuntimeException {
private static final long serialVersionUID = 1L;
public DimensionException(String msg) {
super(msg);
}
}
/**
* Creates the quadratic identical matrix of size n using the given field. The result
* will contain only zeros except for the diagonal components which are set to one.
*
* @param n The size of the matrix.
* @param field The algebraic field which provides the zero and the one element.
* @return An identical matrix of size n.
*/
public static <K> Matrix<K> getId(int n, Field<K> field) {
Matrix<K> result = new Matrix<K>(n, n, field);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (i == j) {
result.matrix[i][j] = field.getMultiplicativeNeutral();
} else {
result.matrix[i][j] = field.getAdditiveNeutral();
}
}
}
return result;
}
/**
* Creates a quadratic matrix of size n over the given field containing only zeros.
*
* @param n The size of the matrix.
* @param field The algebraic field which provides the zero element.
* @return A matrix containing only zeros.
*/
public static <K> Matrix<K> getZero(int n, Field<K> field) {
Matrix<K> result = new Matrix<K>(n, n, field);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
result.matrix[i][j] = field.getAdditiveNeutral();
}
}
return result;
}
/**
* Merges two matrices together. That is, the columns of the right matrix are simply
* appended to the left matrix. This operation is only possible of both matrices have
* the same row count and use the same algebraic field.
*
* <p>The result will be a new matrix of the size m times (left.n + right.n) but it
* will contain the same elements (no copies!) as both source matrices.</p>
*
* @param left The left matrix.
* @param right The right matrix which columns are to be appended to the left matrix.
* @return A new merged matrix.
*/
public static <K> Matrix<K> merge(Matrix<K> left, Matrix<K> right) {
if (left.getM() != right.getM()) {
throw new DimensionException("dimension mismatch");
} else if (!left.getField().equals(right.getField())) {
throw new IllegalArgumentException("incompatible fields");
}
Matrix<K> result = new Matrix<K>(left.getM(), left.getN() + right.getN(),
left.getField());
for (int i = 0; i < left.getM(); ++i) {
for (int j = 0; j < left.getN() + right.getN(); ++j) {
if (j < left.getN()) {
result.set(i, j, left.get(i, j));
} else {
result.set(i, j, right.get(i, j - left.getN()));
}
}
}
return result;
}
public static void main(String[] args) {
Integer[][] m = {
{1, 2, 0, 1, 0, 0},
{2, 3, 0, 0, 1, 0},
{3, 4, 1, 0, 0, 1}
};
Matrix<Integer> matrix = new Matrix<Integer>(m, Fields.integerModulo(5));
System.out.println(splitRight(matrix, 2));
}
/**
* <p>Creates a new matrix by splitting the given one at a certain column and
* returning a matrix which contains all elements to the left of that column. The
* column index is exclusive. Thus {@code splitLeft(matrix, matrix.getN())} will
* return an exact copy of the given matrix.</p>
*
* <p>In general the result will have {@code matrix.getM()} rows and {@code j}
* columns.</p>
*
* @param matrix The matrix to split.
* @param j The (exclusive) column index at which the matrix is splitted.
* @return A new matrix containing the elements to the left of the column denoted by
* {@code j.}
*/
public static <K> Matrix<K> splitLeft(Matrix<K> matrix, int j) {
Matrix<K> result = new Matrix<K>(matrix.getM(), j, matrix.getField());
for (int i = 0; i < matrix.getM(); ++i) {
for (int j1 = 0; j1 < j; ++j1) {
result.set(i, j1, matrix.get(i, j1));
}
}
return result;
}
/**
* <p>Creates a new matrix by splitting the given one at a certain column and
* returning a matrix which contains all elements to the right of that column. The
* column index is inclusive. Thus {@code splitRight(matrix, 0)} will
* return an exact copy of the given matrix.</p>
*
* <p>In general the result will have {@code matrix.getM()} rows and {@code j}
* columns.</p>
*
* @param matrix The matrix to split.
* @param j The (inclusive) column index at which the matrix is splitted.
* @return A new matrix containing the elements to the right of the column denoted by
* {@code j} including column {@code j} itself.
*/
public static <K> Matrix<K> splitRight(Matrix<K> matrix, int j) {
Matrix<K> result = new Matrix<K>(matrix.getM(), matrix.getN() - j,
matrix.getField());
for (int i = 0; i < matrix.getM(); ++i) {
for (int j1 = j; j1 < matrix.getN(); ++j1) {
result.set(i, j1 - j, matrix.get(i, j1));
}
}
return result;
}
private K[][] matrix;
private Field<K> field;
private int m; // row count
private int n; // col count
/**
* <p>Creates a new empty matrix with the given dimensions which uses the given field
* for its algebraic operations.</p>
*
* <p>Please note that empty means, this matrix only contains <code>null</code> and
* thus must be filled by the user using the {@link #set(int, int, Object)}
* method.</p>
*
* @param m The number of rows in this matrix.
* @param n The number of columns in this matrix.
* @param field The algebraic field for this matrix.
*/
@SuppressWarnings("unchecked")
public Matrix(int m, int n, Field<K> field) {
if (m == 0) {
throw new DimensionException("m is 0");
} else if (n == 0) {
throw new DimensionException("n is 0");
}
this.field = field;
this.matrix = (K[][]) new Object[m][n];
this.m = m;
this.n = n;
}
/**
* Creates a new matrix as a copy of the given matrix. It will contain <b>copies</b>
* of each element and thus have the exact same size and will use the same algebraic
* field as the given matrix.
*
* @param other The matrix to copy.
*/
public Matrix(Matrix<K> other) {
this(other.matrix, other.field);
}
/**
* <p>Creates a new matrix which uses the given two-dimensional array as source for
* its elements and the given field for its algebraic operations. The first dimension
* of the array will be interpreted as the rows and the second as the columns.</p>
*
* <p>Please note that the given array is not copied, thus all operations done to
* this matrix are reflected to the array and vice-versa.</p>
*
* @param other The array to create this matrix from.
* @param field The algebraic field for this matrix.
*/
@SuppressWarnings("unchecked")
public Matrix(K[][] other, Field<K> field) {
this.field = field;
this.m = this.getRows(other);
this.n = this.getCols(other);
this.matrix = (K[][]) new Object[this.m][this.n];
for (int i = 0; i < this.m; ++i) {
System.arraycopy(other[i], 0, this.matrix[i], 0, this.n);
}
}
/**
* <p>This constructor can be used to convert a one-dimensional vector into either a
* row- or column based matrix representation.</p>
*
* <p>If creating a column vector, the resulting matrix will have the dimensions
* {@code m = 1} and {@code n = vector.length}. A row vector will have the dimensions
* {@code m = vector.length} and {@code n = 1}</p>
*
* @param vector One-dimensional vector which should be transformed into a matrix
* representation.
* @param isRow Whether the result should be a column- or a row base representation.
* @param field The algebraic field for this matrix.
*/
@SuppressWarnings("unchecked")
public Matrix(K[] vector, boolean isRow, Field<K> field) {
this.field = field;
if (isRow) {
this.m = vector.length;
this.n = 1;
this.matrix = (K[][]) new Object[this.m][this.n];
for (int i = 0; i < this.m; ++i) {
this.matrix[i] = (K[]) new Object[] { vector[i] };
}
} else {
this.m = 1;
this.n = vector.length;
this.matrix = (K[][]) new Object[this.m][this.n];
System.arraycopy(vector, 0, this.matrix[0], 0, this.n);
}
}
/**
* <p>Creates the identical matrix of size m times m where m is the number of rows in
* this matrix.</p>
*
* <p>This is intended to create matrices that can be multiplied with this one.
* Given the matrix A, the product of {@code A.getLeftId()*A} returns A.</p>
*
* @return An identical Matrix of size m times m.
*/
public Matrix<K> getLeftId() {
return getId(this.m, this.field);
}
/**
* <p>Creates the identical matrix of size n times n where n is the number of
* columns in this matrix.</p>
*
* <p>This is intended to create matrices that this matrix can be multiplied with.
* Given the matrix A, the product of {@code A*A.getRightId()} returns A</p>
*
* @return An identical Matrix of size n times n.
*/
public Matrix<K> getRightId() {
return getId(this.n, this.field);
}
/**
* Creates a matrix of size m times m where m is the number of rows in this matrix
* which contains only zeros.
*
* @return A new matrix containing only zeros.
*/
public Matrix<K> getZero() {
return getZero(this.m, this.field);
}
/**
* Gets the underlying {@link Field} of this matrix which provides the elementary
* algebraic operations and values needed for most matrix operations.
*
* @return The underlying field instance.
*/
public Field<K> getField() {
return this.field;
}
/**
* Gets the number of rows of this matrix.
*
* @return The number of rows.
*/
public int getM() {
return this.m;
}
/**
* Gets the number of columns of this matrix.
*
* @return The number of columns.
*/
public int getN() {
return this.n;
}
/**
* Gets the value of the component denoted by i and j. This will throw an
* {@link ArrayIndexOutOfBoundsException} if either i or j exceed the bounds of this
* matrix.
*
* @param i The row index.
* @param j The column index.
* @return The element at the specified coordinates.
*/
public K get(int i, int j) {
return this.matrix[i][j];
}
/**
* <p>Sets the component denoted by i and j of this matrix to k. This will throw an
* {@link ArrayIndexOutOfBoundsException} if either i or j exceed the bounds of this
* matrix.</p>
*
* <p>This setter supports chaining as it returns an instance to 'this'. So it can
* be easily called multiple times on the same matrix like
* {@code matrix.set(a, b, 10).set(c, d, 11).set(e, f, 12)}</p>
*
* @param i The row index.
* @param j The column index.
* @param k The element to set that cell to.
* @return The 'this' reference.
*/
public Matrix<K> set(int i, int j, K k) {
this.matrix[i][j] = k;
return this;
}
/**
* <p>Adds the other matrix to this by adding all elements pairwise.
* This operation is only possible if both matrices have the exact same size.</p>
*
* <p>This operation creates a new matrix for the result and leaves this one
* untouched</p>
*
* @param other The matrix to be added to this one.
* @return A new matrix containing the result.
* @throws DimensionException if the dimensions of this matrix and the other mismatch.
*/
public Matrix<K> add(Matrix<K> other) {
return this.add(other.matrix);
}
/**
* <p>Adds the other matrix given as array to this by adding all elements pairwise.
* This operation is only possible if both matrices have the exact same size.</p>
*
* <p>This operation creates a new matrix for the result and leaves this one
* untouched</p>
*
* @param other The matrix to be added to this one.
* @return A new matrix containing the result.
* @throws DimensionException if the dimensions of this matrix and the other mismatch.
*/
public Matrix<K> add(K[][] other) {
if (!this.canAdd(other)) {
throw new DimensionException("illegal dimension");
}
Matrix<K> result = new Matrix<K>(this.m, this.n, this.field);
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < this.n; ++j) {
result.matrix[i][j] = this.field.add(this.matrix[i][j], other[i][j]);
}
}
return result;
}
/**
* <p>Adds the other matrix to this by adding all elements pairwise.
* This operation is only possible if both matrices have the exact same size.</p>
*
* <p>This operation creates operates in place and the result is stored in this
* matrix instance.
*
* @param other The matrix to be added to this one.
* @return The 'this' reference.
* @throws DimensionException if the dimensions of this matrix and the other mismatch.
*/
public Matrix<K> addInPlace(Matrix<K> other) {
return this.addInPlace(other.matrix);
}
/**
* <p>Adds the other matrix given as array to this by adding all elements pairwise.
* This operation is only possible if both matrices have the exact same size.</p>
*
* <p>This operation operates in place and the result is stored in this
* matrix instance.</p>
*
* @param other The matrix to be added to this one.
* @return The 'this' reference.
* @throws DimensionException if the dimensions of this matrix and the other mismatch.
*/
public Matrix<K> addInPlace(K[][] other) {
if (!this.canAdd(other)) {
throw new DimensionException("illegal dimension");
}
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < this.n; ++j) {
this.matrix[i][j] = this.field.add(this.matrix[i][j], other[i][j]);
}
}
return this;
}
/**
* Determines whether the other matrix can be added to this matrix. That is, if both
* matrices have the same row- and column count.
*
* @param other The matrix to add.
* @return Whether the matrix can be added to this matrix.
*/
public boolean canAdd(Matrix<K> other) {
return this.canAdd(other.matrix);
}
/**
* Determines whether the other matrix can be added to this matrix. That is, if both
* matrices have the same row- and column count.
*
* @param other The matrix to add.
* @return Whether the matrix can be added to this matrix.
*/
public boolean canAdd(K[][] other) {
return this.getRows(other) == this.m && this.getCols(other) == this.n;
}
/**
* Creates the transposed matrix for this one. Given this matrix has the size
* m times n, the size of the result matrix will be n times m.
*
* @return A new matrix containing the result.
*/
public Matrix<K> transpose() {
Matrix<K> result = new Matrix<K>(this.n, this.m, this.field);
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < this.n; ++j) {
result.matrix[i][j] = this.matrix[j][i];
}
}
return result;
}
/**
* If this is a quadratic matrix, this method creates the transposed matrix in place
* by mirroring each element on the diagonal elements.
*
* @return The 'this' reference.
* @throws DimensionException If this matrix is not quadratic.
*/
public Matrix<K> transposeInPlace() {
if (this.m != this.n) {
throw new DimensionException("illegal dimension");
}
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < i; ++j) {
this.swap(i, j, j, i);
}
}
return this;
}
/**
* Multiplies each element in this matrix with the given scalar. This operation
* creates a new matrix for the result and leaves this matrix untouched.
*
* @param scalar The scalar to multiply this matrix with.
* @return A new matrix containing the result.
*/
public Matrix<K> multiplyWith(K scalar) {
Matrix<K> result = new Matrix<K>(this.m, this.n, this.field);
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < this.n; ++j) {
result.matrix[i][j] = this.field.multiply(this.matrix[i][j], scalar);
}
}
return result;
}
/**
* Multiplies each element in this matrix with the given scalar. This method works
* in place and changes the underlying matrix array of this instance.
*
* @param scalar The scalar to multiply this matrix with.
* @return The 'this' reference.
*/
public Matrix<K> multiplyWithInPlace(K scalar) {
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < this.n; ++j) {
this.matrix[i][j] = this.field.multiply(this.matrix[i][j], scalar);
}
}
return this;
}
/**
* <p>Multiplies this matrix with an other matrix. This means that this
* matrix is the left matrix and the other matrix is the right matrix. This matters as
* multiplication of matrices is not commutative. This requires that the column count
* of this matrix equals the row count of the other matrix. If not, a
* {@link DimensionException} is thrown.</p>
*
* <p>This operation creates a new matrix for the result and leaves this and the
* other matrix untouched.</p>
*
* <p>Given this matrix has the size l times m and the others size is m times n, the
* result matrix will have the size l times n.</p>
*
* @param other The matrix to multiply this with.
* @return A new matrix with the result of the multiplication.
*/
public Matrix<K> multiplyWith(Matrix<K> other) {
return this.multiplyWith(other.matrix);
}
/**
* <p>Multiplies this matrix with an other matrix given as array. This means that this
* matrix is the left matrix and the other matrix is the right matrix. This matters as
* multiplication of matrices is not commutative. This requires that the column count
* of this matrix equals the row count of the other matrix. If not, a
* {@link DimensionException} is thrown.</p>
*
* <p>This operation creates a new matrix for the result and leaves this and the
* other matrix untouched.</p>
*
* <p>Given this matrix has the size l times m and the others size is m times n, the
* result matrix will have the size l times n.</p>
*
* @param other The matrix to multiply this with.
* @return A new matrix with the result of the multiplication.
*/
public Matrix<K> multiplyWith(K[][] other) {
if (!this.canMultiplyWith(other)) {
throw new DimensionException("illegal dimension");
}
Matrix<K> result = new Matrix<K>(this.m, this.getCols(other), this.field);
K zero = this.field.getAdditiveNeutral();
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < this.getCols(other); ++j) {
K sum = zero;
for (int k = 0; k < this.n; ++k) {
sum = this.field.add(sum,
this.field.multiply(this.matrix[i][k], other[k][j]));
}
result.matrix[i][j] = sum;
}
}
return result;
}
/**
* Multiplies the row 'row' with scalar.
*
* @param scalar The scalar to multiply the row with.
* @param row The row index.
*/
public void multiplyRowWith(K scalar, int row) {
for (int j = 0; j < this.n; ++j) {
this.matrix[row][j] = this.field.multiply(scalar, this.matrix[row][j]);
}
}
/**
* Adds the product of scalar and each element of the row srcRow to the row destRow.
*
* @param scalar The scalar to multiply the source row with.
* @param srcRow The row to multiply with the scalar.
* @param destRow The row to which the product of the multiplication is added.
*/
public void addMultipleToRow(K scalar, int srcRow, int destRow) {
for (int j = 0; j < this.n; ++j) {
this.matrix[destRow][j] = this.field.add(this.matrix[destRow][j],
this.field.multiply(scalar, this.matrix[srcRow][j]));
}
}
/**
* Swaps two rows of this matrix. That is done in constant time, as only two array
* entries of the two-dimensional matrix must be swapped.
*
* @param srcRow The first row.
* @param destRow The second row.
*/
public void swapRows(int srcRow, int destRow) {
K[] tmp = this.matrix[srcRow];
this.matrix[srcRow] = this.matrix[destRow];
this.matrix[destRow] = tmp;
}
/**
* Swaps two elements of this matrix.
*
* @param srcRow The row index of the first element.
* @param srcCol The column index of the first element
* @param destRow The row index of the second element.
* @param destCol The column index of the second element.
*/
public void swap(int srcRow, int srcCol, int destRow, int destCol) {
K tmp = this.matrix[srcRow][srcCol];
this.matrix[srcRow][srcCol] = this.matrix[destRow][destCol];
this.matrix[destRow][destCol] = tmp;
}
/**
* Determines whether this matrix can be multiplied with the other matrix. This is the
* case if this matrix' column count equals the other matrix' row count.
*
* @param other The matrix to multiply this with.
* @return Whether this matrix can be multiplied with the other matrix.
*/
public boolean canMultiplyWith(Matrix<K> other) {
return this.canMultiplyWith(other.matrix);
}
/**
* Determines whether this matrix can be multiplied with the other matrix given as
* array. This is the case if this matrix' column count equals the other matrix' row
* count.
*
* @param other The matrix to multiply this with.
* @return Whether this matrix can be multiplied with the other matrix.
*/
public boolean canMultiplyWith(K[][] other) {
return this.n == this.getRows(other);
}
private int getRows(K[][] other) {
return other.length;
}
private int getCols(K[][] other) {
return other[0].length;
}
/**
* Determines whether this is the zero matrix. This is the case if this matrix
* contains zero in every element.
*
* @return <code>true</code> iff this matrix contains only zeros.
*/
public boolean isZero() {
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < this.n; ++j) {
if (!this.matrix[i][j].equals(this.field.getAdditiveNeutral())) {
return false;
}
}
}
return true;
}
/**
* Determines whether this matrix is the identical matrix. That is, it must be
* quadratic and may contain only zeros except on its diagonal where it must have
* ones.
*
* @return <code>true</code> if this matrix is quadratic and the identical matrix.
*/
public boolean isId() {
if (this.m != this.n) {
return false;
}
K zero = this.field.getAdditiveNeutral();
K one = this.field.getMultiplicativeNeutral();
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < this.n; ++j) {
if (i == j && !this.matrix[i][j].equals(one)) {
return false;
} else if (!this.matrix.equals(zero)) {
return false;
}
}
}
return true;
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < this.n; ++j) {
b.append(this.matrix[i][j] == null ? "null" : this.matrix[i][j].toString());
if (j < this.n - 1) {
b.append(" ");
}
}
b.append("\n");
}
return b.toString();
}
/**
* Creates a formatted String from this matrix in which each element is aligned using
* the length of the longest entry in the same column. All columns will be
* right-aligned and separated by a single space.
*
* @return A formatted String representation of this matrix.
* @see #toAlignedString(String, String, boolean)
*/
public String toAlignedString() {
return this.toAlignedString(" ", "", false);
}
/**
* Creates a formatted String from this matrix in which the alignment and delimiter
* characters can be chosen. Columns are aligned by padding each element by a number
* of spaces until its length equals the length of the longest entry in the same
* column. The user can choose the column delimiter and additionally a string which is
* put after each line. This can be useful for example to produce a LaTeX
* representation of this matrix (this.toAlignedString(" & ", "\\\\", true) will
* return a String suitable for LaTex documents).
*
* @param colDelimiter Column delimiter which is put between each column. If empty,
* columns are not separated.
* @param rowEnd A String which is put at the end of each row before the newline
* character.
* @param alignLeft If set to true, each column will be left aligned instead of right.
* @return A formatted String representation of this matrix.
*/
public String toAlignedString(String colDelimiter, String rowEnd, boolean alignLeft) {
int[] colLengths = new int[this.n];
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < this.n; ++j) {
colLengths[j] = Math.max(colLengths[j],
this.matrix[i][j].toString().length());
}
}
StringBuilder b = new StringBuilder();
for (int i = 0; i < this.m; ++i) {
for (int j = 0; j < this.n; ++j) {
int desiredLength = colLengths[j];
String s = this.matrix[i][j].toString();
if (alignLeft) {
b.append(s);
StringUtils.padSpaces(desiredLength, s.length(), b);
} else {
StringUtils.padSpaces(desiredLength, s.length(), b);
b.append(s);
}
if (j < this.n - 1) {
b.append(colDelimiter);
}
}
b.append(rowEnd);
if (i < this.m - 1) {
b.append("\n");
}
}
return b.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((this.field == null) ? 0 : this.field.hashCode());
result = prime * result + Arrays.hashCode(this.matrix);
return result;
}
@SuppressWarnings("rawtypes")
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Matrix)) {
return false;
}
Matrix other = (Matrix) obj;
if (this.field == null) {
if (other.field != null) {
return false;
}
} else if (!this.field.equals(other.field)) {
return false;
} else if (this.m != other.m || this.n != other.n) {
return false;
}
for (int i = 0; i < this.m; ++i) {
if (!Arrays.equals(this.matrix[i], other.matrix[i])) {
return false;
}
}
return true;
}
}