/*
* 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();
}
}
}