package org.molgenis.matrix;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* Simple 'in-memory' implementation of the Matrix. Data is stored as
* two-dimensional array.
*
* @param <E>
*/
public class MemoryMatrix<E, A, V> implements Matrix<E, A, V>
{
protected V[][] values;
private List<E> rowNames = new ArrayList<E>();
private List<A> colNames = new ArrayList<A>();
private Class<V> valueType = null;
/** Creata an empty matrix using dimensions */
public MemoryMatrix(List<E> rowNames, List<A> colNames, Class<V> valueType) throws MatrixException
{
this.valueType = valueType;
// add row metadata
this.setColNames(colNames);
this.setRowNames(rowNames);
// set the values
this.setValues(this.create(rowNames.size(), colNames.size(), valueType));
}
@SuppressWarnings("unchecked")
public MemoryMatrix(List<E> rowNames, List<A> colNames, V[][] values) throws MatrixException
{
// get from a not-null value the valueType
try
{
this.valueType = (Class<V>) getValueType(values);
}
catch (MatrixException e)
{
// Discussion: what to do here? Do we need the valueType when there
// are only null-values
// in the current (sub)matrix? Could we maybe take
// ObservedValue.class as default?
}
// add row metadata
this.setColNames(colNames);
this.setRowNames(rowNames);
// set the values
this.setValues(values);
}
@SuppressWarnings("unchecked")
private Class<?> getValueType(V[][] values) throws MatrixException
{
for (int i = 0; i < values.length; i++)
{
for (int j = 0; j < values[i].length; j++)
{
if (values[i][j] != null)
{
return (Class<V>) values[i][j].getClass();
}
}
}
throw new MatrixException("MemoryMatrix needs at least one value in the [][] to establish type");
}
/** Protected constructor for the subclasses */
protected MemoryMatrix()
{
}
/**
* Copy constructor
*
* @param values
* @throws MatrixException
*/
public MemoryMatrix(Matrix<E, A, V> matrix) throws MatrixException
{
this(matrix.getRowNames(), matrix.getColNames(), matrix.getValues());
}
@Override
public V[] getCol(int i) throws MatrixException
{
V[] result = create(getRowCount());
try
{
for (int j = 0; j < getRowCount(); j++)
{
result[j] = getValue(j, i);
}
}
catch (ArrayIndexOutOfBoundsException e)
{
throw new MatrixException("column with index " + i + " doesn't exist");
}
return result;
}
@Override
public V[] getRow(int i) throws MatrixException
{
try
{
return getValues()[i].clone();
}
catch (ArrayIndexOutOfBoundsException e)
{
throw new MatrixException("row with index " + i + " doesn't exist");
}
}
@Override
public V getValue(int row, int col) throws MatrixException
{
if (row >= this.getRowCount()) throw new MatrixException("row > rowCount");
if (col >= this.getColCount()) throw new MatrixException("col > colCount");
return this.values[row][col];
}
@Override
public Matrix<E, A, V> getSubMatrixByOffset(int row, int nrows, int col, int ncols) throws MatrixException
{
List<E> rows = new ArrayList<E>(nrows);
List<A> cols = new ArrayList<A>(ncols);
V[][] elements = (V[][]) create(nrows, ncols, this.valueType);
V[][] allAlements = this.getValues();
rows = this.getRowNames().subList(row, row + nrows);
cols = this.getColNames().subList(col, col + ncols);
for (int rowIndex = row; rowIndex < row + nrows; rowIndex++)
{
for (int colIndex = col; colIndex < col + ncols; colIndex++)
{
elements[rowIndex - row][colIndex - col] = allAlements[rowIndex][colIndex];
}
}
return new MemoryMatrix<E, A, V>(rows, cols, elements);
}
@Override
public Matrix<E, A, V> getSubMatrixByIndex(List<Integer> rowIndices, List<Integer> colIndices)
throws MatrixException
{
List<E> rows = new ArrayList<E>(rowIndices.size());
List<A> cols = new ArrayList<A>(colIndices.size());
// create placeholder for new values + existing values
V[][] elements = (V[][]) create(rowIndices.size(), colIndices.size(), this.valueType);
V[][] allAlements = this.getValues();
// set colnames
for (int col : colIndices)
{
cols.add(this.getColNames().get(col));
}
// set rownames and values
int rowIndex = 0;
int colIndex = 0;
for (int row : rowIndices)
{
rows.add(this.getRowNames().get(row));
colIndex = 0;
for (int col : colIndices)
{
elements[rowIndex][colIndex] = allAlements[row][col];
colIndex++;
}
rowIndex++;
}
return new MemoryMatrix<E, A, V>(rows, cols, elements);
}
@Override
public int getColCount()
{
return this.colNames.size();
}
@Override
public List<A> getColNames() throws MatrixException
{
// return a copy
return new ArrayList<A>(this.colNames);
}
@Override
public int getRowCount()
{
return this.rowNames.size();
}
@Override
public List<E> getRowNames() throws MatrixException
{
return new ArrayList<E>(this.rowNames);
}
@Override
public Matrix<E, A, V> getSubMatrixByName(List<E> rowSelection, List<A> colSelection) throws MatrixException
{
if (rowSelection == null || rowSelection.size() == 0) throw new MatrixException(this.getClass().getSimpleName()
+ ".getSubMatrixByName() failed: rowSelection was empty");
if (colSelection == null || colSelection.size() == 0) throw new MatrixException(this.getClass().getSimpleName()
+ ".getSubMatrixByName() failed: colSelection was empty");
List<Integer> rowDimensions = new ArrayList<Integer>();
List<Integer> colDimensions = new ArrayList<Integer>();
// get row dimensions
for (E rowName : rowSelection)
{
rowDimensions.add(getRowId(rowName));
}
// get column dimensions rowIndices
for (A colName : colSelection)
{
colDimensions.add(getColId(colName));
}
return this.getSubMatrixByIndex(rowDimensions, colDimensions);
}
public Integer getRowId(E rowName) throws MatrixException
{
int rowid = this.rowNames.indexOf(rowName);
if (rowid == -1) throw new MatrixException("couldn't find row by name " + rowName);
return rowid;
}
public Integer getColId(A colName) throws MatrixException
{
int colid = this.colNames.indexOf(colName);
if (colid == -1) throw new MatrixException("couldn't find col by name " + colName);
return colid;
}
@Override
public V getValue(E rowName, A colName) throws MatrixException
{
return this.getValue(getRowId(rowName), getColId(colName));
}
@Override
public Matrix<A, E, V> transpose() throws MatrixException
{
// copy the data, swapped
V[][] newValues = (V[][]) create(this.rowNames.size(), this.colNames.size(), this.valueType);
for (int i = 0; i < this.getValues().length; i++)
{
for (int j = 0; j < this.getValues().length; j++)
{
newValues[j][i] = this.getValues()[i][j];
}
}
// return a new memory matrix
return new MemoryMatrix<A, E, V>(this.colNames, this.rowNames, newValues);
}
public V[][] getValues() throws MatrixException
{
return this.values;
}
protected void setValues(V[][] values) throws MatrixException
{
// checks number of rows
if (getRowCount() != values.length) throw new MatrixException(
"rows(values) and getRowCount() are of different sizes: rowCount=" + getRowCount()
+ " vs value.lenght=" + values.length);
int i = 0;
// check length of each row to be equal to number of columns
for (V[] row : values)
{
if (getColCount() != row.length) throw new MatrixException("values on row=" + (i + 1)
+ " has a different size than getColCount(): " + getColCount() + " vs " + row.length);
i++;
}
this.values = values;
}
public void setValue(E row, A col, V value) throws MatrixException
{
this.values[getRowId(row)][getColId(col)] = value;
}
public void setValue(int row, int col, V value) throws MatrixException
{
this.values[row][col] = value;
}
@SuppressWarnings("unchecked")
protected V[] create(int rows)
{
return (V[]) new Object[rows];
}
@SuppressWarnings("unchecked")
public V[][] create(int rows, int cols, Class<V> valueType)
{
// create all empty rows as well
V[][] data = (V[][]) Array.newInstance(valueType, rows, cols);
for (int i = 0; i < data.length; i++)
{
data[i] = (V[]) Array.newInstance(valueType, cols);
}
return data;
}
protected void setRowNames(List<E> rowNames) throws MatrixException
{
resetRows();
// add row metadata
for (E rowName : rowNames)
{
if (this.rowNames.contains(rowName))
{
throw new MatrixException("RowNames must be unique");
}
this.rowNames.add(rowName);
}
}
/**
* Change column names.
*
* @param colNames
* @throws MatrixException
*/
protected void setColNames(List<A> colNames) throws MatrixException
{
// clean existing metadata
resetCols();
for (A colName : colNames)
{
if (this.colNames.contains(colName))
{
throw new MatrixException("column names must be unique");
}
this.colNames.add(colName);
}
}
private void resetRows()
{
this.rowNames = new ArrayList<E>();
}
private void resetCols()
{
this.colNames = new ArrayList<A>();
}
@Override
public V[] getColByName(A colName) throws MatrixException
{
return getCol(getColId(colName));
}
@Override
public V[] getRowByName(E name) throws MatrixException
{
return getRow(getRowId(name));
}
@SuppressWarnings("unchecked")
@Override
public Class<V> getValueType() throws MatrixException
{
return (Class<V>) this.values.getClass().getComponentType().getComponentType();
}
@Override
public List<A> getColNamesByOffset(int index, int offset) throws MatrixException
{
return this.getColNames().subList(index, index + offset);
}
}