/* * Matrix.java * * Copyright (c) 2009 by Kevin McGuinness * * TODO: Optimizations in subclasses (toXMatrix) */ package ie.dcu.matrix; import java.io.*; import java.util.*; import ie.dcu.array.Arrays; /** * Abstract base class for two dimensional matrix types. * * @author Kevin McGuinness */ public abstract class Matrix implements Cloneable, Serializable, MatrixProvider { /** * Serialization UID. */ private static final long serialVersionUID = 7948337494015379755L; /** * Supported matrix types. */ public static enum Type { Byte, Short, Int, Long, Double, Float }; /** * The matrix type. (never null). */ public final Type type; /** * The number of rows (height) of the matrix (always >= 0). */ public final int rows; /** * The number of rows (width) of the matrix (always >= 0). */ public final int cols; /** * The total number of elements matrix (always >= 0). */ public final int size; /** * Constructor for subclasses. * * @param type * The matrix type (cannot be null). * @param rows * The number or rows in the matrix (must be >= 0). * @param cols * The number of columns in the matrix (must be >= 0). */ protected Matrix(Type type, int rows, int cols) { this(type, rows, cols, rows * cols); } /** * Constructor for subclasses. * * The reason this constructor takes a size parameter is to allow * subclasses to easily ensure the size of a passed array is correct. * * @param type * The matrix type (cannot be null). * @param rows * The number or rows in the matrix (must be >= 0). * @param cols * The number of columns in the matrix (must be >= 0). * @param size * Must equal rows * cols. */ protected Matrix(Type type, int rows, int cols, int size) { if (type == null) { throw new IllegalArgumentException("type == null"); } if (rows < 0) { throw new IllegalArgumentException("rows < 0"); } if (cols < 0) { throw new IllegalArgumentException("cols < 0"); } if (size != rows * cols) { throw new IllegalArgumentException("size != rows * cols"); } this.type = type; this.rows = rows; this.cols = cols; this.size = size; } /** * Returns true if the given row index is in range. * * @param row * The row index */ public final boolean hasRow(int row) { return row >= 0 && row < rows; } /** * Returns true if the given column index is in range. * * @param col * The column index */ public final boolean hasCol(int col) { return col >= 0 && col < cols; } /** * Returns true if the given row and column index are in range. * * @param row * The row index * @param col * The column index */ public final boolean hasIndex(int row, int col) { return row >= 0 && row < rows && col >= 0 && col < cols; } /** * Returns true if the given two dimensional index is in range. * * @param index * The two dimensional index */ public final boolean hasIndex(Index2D index) { return hasIndex(index.i, index.j); } /** * Returns true if the given offset is in range. * * @param offset * An absolute offset into the matrix. */ public final boolean hasOffset(int offset) { return offset >= 0 && offset < size; } /** * Returns true if the given matrix is the same size as the receiver. * * @param m * A matrix of any type. */ public final boolean sizeEquals(Matrix m) { return rows == m.rows && cols == m.cols; } /** * Returns the width (number of columns) in the matrix. */ public final int width() { return cols; } /** * Returns the height (number of rows) in the matrix. */ public final int height() { return rows; } /** * Returns the offset of the given row index. * * @param row * The row index. */ public final int offsetOf(int row) { return row * cols; } /** * Returns the offset of the given row and column index. * * For efficiency this method does not check if the matrix is empty. * The value col will be returned if it is. * * @param row * The row index. * @param col * The column index. */ public final int offsetOf(int row, int col) { return row * cols + col; } /** * Returns the offset of the given two dimensional index. * * For efficiency this method does not check if the matrix is empty. * The value index.j will be returned if it is. * * @param index * A two dimensional index. */ public final int offsetOf(Index2D index) { return index.i * cols + index.j; } /** * Returns the two dimensional index of the given offset. * * The return value is null if the matrix is empty. * * @param offset * An absolute offset into the matrix. * @return * The two dimensional index of the offset. */ public final Index2D indexOf(int offset) { return size != 0 ? new Index2D(offset / cols, offset % cols) : null; } /** * Returns true of the matrix has zero rows or zero columns. */ public final boolean isEmpty() { return size == 0; } /** * Returns the Number value at the given row and column. * * @param row * The row index. * @param col * The column index. * @return * A Number value. */ public Number valueAt(int row, int col) { return valueAt(offsetOf(row, col)); } /** * Returns the Number value at the given two dimensional index. * * @param index * The two dimensional index. * @return * A Number value. */ public Number valueAt(Index2D index) { return valueAt(offsetOf(index)); } /** * Set the Number value at the given row and column. * * @param row * The row index. * @param col * The column index. * @param value * The Number value to set. */ public void setValueAt(int row, int col, Number value) { setValueAt(offsetOf(row, col), value); } /** * Set the Number value at the given two dimensional index. * * @param index * The two dimensional index * @param value * The Number value to set. */ public void setValueAt(Index2D index, Number value) { setValueAt(offsetOf(index), value); } /** * Fill the matrix with the given number value. * * @param number * The number value. * @return This matrix. */ public Matrix fill(Number number) { for (int i = 0; i < size; i++) { setValueAt(i, number); } return this; } /** * Clamp the matrix values in the given range. * * @param min * The minimum value. * @param max * The maximum value. * @return This matrix. */ public Matrix clamp(Number min, Number max) { Arrays.clamp(values(), min, max); return this; } /** * Returns the smallest value in the matrix. * * Comparisons are performed in double precision. * * @return * The smallest value, or null if the * matrix is empty. */ public Number minValue() { Number min = null; if (!isEmpty()) { min = valueAt(0); for (int i = 1; i < size; i++) { Number value = valueAt(i); if (value.doubleValue() < min.doubleValue()) { min = value; } } } return min; } /** * Returns the largest value in the matrix. * * Comparisons are performed in double precision. * * @return * The largest value, or null if the * matrix is empty. */ public Number maxValue() { Number max = null; if (!isEmpty()) { max = valueAt(0); for (int i = 1; i < size; i++) { Number value = valueAt(i); if (value.doubleValue() > max.doubleValue()) { max = value; } } } return max; } /** * Find the first index containing the given number. If no such index * exists, null is returned. * * Comparison is performed using double precision floats. * * @param number * A Number value. * @return * The first index matching the value, or null if none is found. */ public Index2D findFirst(Number number) { double value = number.doubleValue(); for (int i = 0, k = 0; i < rows; i++) { for (int j = 0; j < cols; j++, k++) { if (valueAt(k).doubleValue() == value) { return new Index2D(i, j); } } } return null; } /** * Find the next index containing the given number. If no such index * exists, null is returned. * * Comparison is performed using double precision floats. * * @param number * A Number value. * @param start * The index to start at. * @return * The first index matching the value, or null if none is found. */ public Index2D findNext(Index2D start, Number number) { double value = number.doubleValue(); for (int i = start.i; i < rows; i++) { int k = i * cols; for (int j = start.j; j < cols; j++, k++) { if (valueAt(k).doubleValue() == value) { return new Index2D(i, j); } } } return null; } /** * Find the last index containing the given number. If no such index * exists, null is returned. * * Comparison is performed using double precision floats. * * @param number * A Number value. * @return * The last index matching the value, or null if none is found. */ public Index2D findLast(Number number) { double value = number.doubleValue(); for (int i = rows - 1; i >= 0; i--) { for (int j = cols - 1; j >= 0; j--) { if (valueAt(i,j).doubleValue() == value) { return new Index2D(i, j); } } } return null; } /** * Find all indices containing the given number. * * The returned list is ordered from first found to last, with the search * starting at the top left corner of the matrix and proceeding in row major * order. * * Comparison is performed using double precision floats. * * @param number * A Number value. * @return * A list of indices. */ public List findAll(Number number) { ArrayList indices = new ArrayList(); double value = number.doubleValue(); for (int i = 0, k = 0; i < rows; i++) { for (int j = 0; j < cols; j++, k++) { if (valueAt(k).doubleValue() == value) { indices.add(new Index2D(i, j)); } } } return indices; } /** * Convert to a ByteMatrix, rounding and truncating if necessary. * * @return * A new ByteMatrix. */ public ByteMatrix toByteMatrix() { return toByteMatrix(null); } /** * Convert to a ByteMatrix, rounding and truncating if necessary. * * If the specified matrix is null or not the same * size as this matrix, a new matrix is allocated. * * @param matrix * The matrix to copy to. * @return * A new ByteMatrix. */ public ByteMatrix toByteMatrix(ByteMatrix matrix) { if (matrix == null || !sizeEquals(matrix)) { matrix = new ByteMatrix(rows, cols); } Arrays.copy(values(), matrix.values); return matrix; } /** * Convert to a ShortMatrix, rounding and truncating if necessary. * * @return * A new ShortMatrix. */ public ShortMatrix toShortMatrix() { return toShortMatrix(null); } /** * Convert to a ShortMatrix, rounding and truncating if necessary. * * If the specified matrix is null or not the same * size as this matrix, a new matrix is allocated. * * @param matrix * The matrix to copy to. * @return * A new ShortMatrix. */ public ShortMatrix toShortMatrix(ShortMatrix matrix) { if (matrix == null || !sizeEquals(matrix)) { matrix = new ShortMatrix(rows, cols); } Arrays.copy(values(), matrix.values); return matrix; } /** * Convert to an IntMatrix, rounding and truncating if necessary. * * @return * A new IntMatrix. */ public IntMatrix toIntMatrix() { return toIntMatrix(null); } /** * Convert to a IntMatrix, rounding and truncating if necessary. * * If the specified matrix is null or not the same * size as this matrix, a new matrix is allocated. * * @param matrix * The matrix to copy to. * @return * A new IntMatrix. */ public IntMatrix toIntMatrix(IntMatrix matrix) { if (matrix == null || !sizeEquals(matrix)) { matrix = new IntMatrix(rows, cols); } Arrays.copy(values(), matrix.values); return matrix; } /** * Convert to a LongMatrix, rounding and truncating if necessary. * * @return * A new LongMatrix. */ public LongMatrix toLongMatrix() { return toLongMatrix(null); } /** * Convert to a LongMatrix, rounding and truncating if necessary. * * If the specified matrix is null or not the same * size as this matrix, a new matrix is allocated. * * @param matrix * The matrix to copy to. * @return * A new LongMatrix. */ public LongMatrix toLongMatrix(LongMatrix matrix) { if (matrix == null || !sizeEquals(matrix)) { matrix = new LongMatrix(rows, cols); } Arrays.copy(values(), matrix.values); return matrix; } /** * Convert to an FloatMatrix, rounding if necessary. * * @return * A new FloatMatrix. */ public FloatMatrix toFloatMatrix() { return toFloatMatrix(null); } /** * Convert to an FloatMatrix, rounding if necessary. * * If the specified matrix is null or not the same * size as this matrix, a new matrix is allocated. * * @param matrix * The matrix to copy to. * @return * A new FloatMatrix. */ public FloatMatrix toFloatMatrix(FloatMatrix matrix) { if (matrix == null || !sizeEquals(matrix)) { matrix = new FloatMatrix(rows, cols); } Arrays.copy(values(), matrix.values); return matrix; } /** * Convert to an DoubleMatrix, rounding if necessary. * * @return * A new DoubleMatrix. */ public DoubleMatrix toDoubleMatrix() { return toDoubleMatrix(null); } /** * Convert to an DoubleMatrix, rounding if necessary. * * If the specified matrix is null or not the same * size as this matrix, a new matrix is allocated. * * @param matrix * The matrix to copy to. * @return * A new DoubleMatrix. */ public DoubleMatrix toDoubleMatrix(DoubleMatrix matrix) { if (matrix == null || !sizeEquals(matrix)) { matrix = new DoubleMatrix(rows, cols); } Arrays.copy(values(), matrix.values); return matrix; } /** * Returns the Number value at the given offset. * * @param offset * An absolute offset into the matrix. * @return * The Number value at the offset. */ public abstract Number valueAt(int offset); /** * Set the Number value at the given offset. * * @param offset * An absolute offset into the matrix. * @param value * The number value to set. */ public abstract void setValueAt(int offset, Number value); /** * Returns true of the matrices are the same size and have equal values. */ @Override public boolean equals(Object obj) { if (obj instanceof Matrix) { Matrix m = (Matrix) obj; if (sizeEquals(m)) { for (int i = 0; i < size; i++) { if (!valueAt(i).equals(m.valueAt(i))) { return false; } } return true; } } return false; } /** * Returns the array of matrix values. The return type is dependent * on the subclass, so Object is given here. * * @return The array of values. */ public abstract Object values(); /** * Returns a string representation of the matrix. */ @Override public String toString() { String header = String.format("%dx%d Matrix (%s)", rows, cols, type); if (rows < 10 && cols < 10) { // Show the whole matrix StringBuffer s = new StringBuffer(header); s.append(" = [\n"); MatrixFormatter f = new MatrixFormatter(); f.format(this, s); s.append(" ]"); return s.toString(); } return header; } /* * (non-Javadoc) * @see ie.dcu.cdvp.matrix.MatrixProvider#getDefaultMatrixType() */ public Type getDefaultMatrixType() { return type; } /* * (non-Javadoc) * @see ie.dcu.cdvp.matrix.MatrixProvider#getMatrix(boolean) */ public Matrix getMatrix(boolean alwaysCopy) { return getMatrix(null, alwaysCopy); } /* * (non-Javadoc) * @see ie.dcu.cdvp.matrix.MatrixProvider#getMatrix(ie.dcu.cdvp.matrix.Matrix.Type, boolean) */ public Matrix getMatrix(Type type, boolean alwaysCopy) { if (type == null || type == this.type) { try { return (alwaysCopy) ? (Matrix) clone() : this; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } switch (type) { case Byte: return toByteMatrix(); case Short: return toShortMatrix(); case Int: return toIntMatrix(); case Long: return toLongMatrix(); case Float: return toFloatMatrix(); case Double: return toDoubleMatrix(); default: throw new UnsupportedOperationException(); } } }