/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics;
import java.util.Arrays;
import org.apache.commons.lang.Validate;
import com.opengamma.financial.analytics.QuickSorter.ArrayQuickSorter;
/**
*
* @param <S> The type of the keys
* @param <T> The type of the tolerance
*/
//TODO need to test for uniqueness of keys and labels
public abstract class LabelledMatrix1D<S extends Comparable<? super S>, T> {
private final String _labelsTitle;
private final String _valuesTitle;
private final S[] _keys;
private final Object[] _labels;
private final double[] _values;
private final T _defaultTolerance;
public LabelledMatrix1D(final S[] keys, final double[] values, final T defaultTolerance) {
this(keys, LabelledMatrixUtils.toString(keys), values, defaultTolerance);
}
public LabelledMatrix1D(final S[] keys, final Object[] labels, final double[] values, final T defaultTolerance) {
this(keys, labels, null, values, null, defaultTolerance);
}
public LabelledMatrix1D(final S[] keys, final String labelsTitle, final double[] values, final String valuesTitle, final T defaultTolerance) {
this(keys, LabelledMatrixUtils.toString(keys), labelsTitle, values, valuesTitle, defaultTolerance);
}
public LabelledMatrix1D(final S[] keys, final Object[] labels, final String labelsTitle, final double[] values, final String valuesTitle, final T defaultTolerance) {
Validate.notNull(keys, "labels");
Validate.notNull(labels, "label names");
Validate.notNull(values, "values");
final int n = keys.length;
Validate.isTrue(n == labels.length, "length of keys array must match length of label names array");
Validate.isTrue(n == values.length, "length of keys array must match length of values array");
_keys = Arrays.copyOf(keys, n);
_labels = Arrays.copyOf(labels, n);
_labelsTitle = labelsTitle;
_values = Arrays.copyOf(values, n);
_valuesTitle = valuesTitle;
_defaultTolerance = defaultTolerance;
quickSort();
}
public S[] getKeys() {
return _keys;
}
public Object[] getLabels() {
return _labels;
}
public String getLabelsTitle() {
return _labelsTitle;
}
public double[] getValues() {
return _values;
}
public String getValuesTitle() {
return _valuesTitle;
}
public int size() {
return _keys.length;
}
/**
* Adds a labelled matrix to this one and returns a new matrix.
* <p>
* Each key in the new matrix is checked to see if it is in the original; if so, the value for that key is added. If the key is not present,
* the new key, label and value are attached to the end of the matrix. This method ignores the label - if there is a key already present but
* the labels do not match, then the new label is the original. For example, if there is an entry (3, "3", 0.1) and an entry (3, "THREE", 0.5) in
* the new matrix, the result will be (3, "3", 0.6)
* @param other Another labelled matrix
* @return The sum of the matrices
*/
public LabelledMatrix1D<S, T> addIgnoringLabel(final LabelledMatrix1D<S, T> other) {
return addIgnoringLabel(other, getDefaultTolerance());
}
/**
* Adds a labelled matrix to this one and returns a new matrix.
* <p>
* Each key in the new matrix is checked to see if it is in the original; if so, the value for that key is added. If the key is not present,
* the new key, label and value are attached to the end of the matrix. This method ignores the label - if there is a key already present but
* the labels do not match, then the new label is the original. For example, if there is an entry (3, "3", 0.1) and an entry (3, "THREE", 0.5) in
* the new matrix, the result will be (3, "3", 0.6).
* @param other Another labelled matrix
* @param tolerance The tolerance
* @return The sum of the matrices
*/
public LabelledMatrix1D<S, T> addIgnoringLabel(final LabelledMatrix1D<S, T> other, final T tolerance) {
return add(other, tolerance, true);
}
/**
* Adds a labelled matrix to this one and returns a new matrix.
* <p>
* Each key in the new matrix is checked to see if it is in the original; if so, the value for that key is added. If the key is not present,
* the new key, label and value are attached to the end of the matrix. This method does not ignores the label - if there is a key already present but
* the labels do not match, then an exception is thrown.
* @param other Another labelled matrix, not null
* @return The sum of the matrices
*/
public LabelledMatrix1D<S, T> add(final LabelledMatrix1D<S, T> other) {
return add(other, getDefaultTolerance());
}
/**
* Adds a labelled matrix to this one and returns a new matrix.
* <p>
* Each key in the new matrix is checked to see if it is in the original; if so, the value for that key is added. If the key is not present,
* the new key, label and value are attached to the end of the matrix. This method does not ignores the label - if there is a key already present but
* the labels do not match, then an exception is thrown.
* @param other Another labelled matrix, not null
* @param tolerance The tolerance
* @return The sum of the matrices
*/
public LabelledMatrix1D<S, T> add(final LabelledMatrix1D<S, T> other, final T tolerance) {
return add(other, tolerance, false);
}
/**
* Adds a key, label and value to this matrix, returning a new matrix.
* <p>
* Each key in the new matrix is checked to see if it is in the original; if so, the value for that key is added. If the key is not present,
* the new key, label and value are attached to the end of the matrix. This method ignores the label - if there is a key already present but
* the labels do not match, then the new label is the original. For example, if there is an entry (3, "3", 0.1) and an entry (3, "THREE", 0.5) in
* the new matrix, the result will be (3, "3", 0.6)
* @param key The key to which a value is to be added
* @param label The label for the key
* @param value The value to add
* @return The sum of the matrices
*/
public LabelledMatrix1D<S, T> addIgnoringLabel(final S key, final Object label, final double value) {
return addIgnoringLabel(key, label, value, getDefaultTolerance());
}
/**
* Adds a key, label and value to this matrix, returning a new matrix.
* <p>
* Each key in the new matrix is checked to see if it is in the original; if so, the value for that key is added. If the key is not present,
* the new key, label and value are attached to the end of the matrix. This method ignores the label - if there is a key already present but
* the labels do not match, then the new label is the original. For example, if there is an entry (3, "3", 0.1) and an entry (3, "THREE", 0.5) in
* the new matrix, the result will be (3, "3", 0.6)
* @param key The key to which a value is to be added
* @param label The label for the key
* @param value The value to add
* @param tolerance The tolerance
* @return The sum of the matrices
*/
public LabelledMatrix1D<S, T> addIgnoringLabel(final S key, final Object label, final double value, final T tolerance) {
return add(key, label, value, tolerance, true);
}
/**
* Adds a key, label and value to this matrix, returning a new matrix.
* <p>
* The key is checked to see if it is in the original; if so, the value for that key is added. If the key is not present,
* the new key, label and value are attached to the end of the matrix. This method does not ignores the label - if there is a key already present but
* the labels do not match, then an exception is thrown.
* @param key The key to which a value is to be added
* @param label The label for the key
* @param value The value to add
* @return The sum of the matrices
*/
public LabelledMatrix1D<S, T> add(final S key, final Object label, final double value) {
return add(key, label, value, getDefaultTolerance());
}
/**
* Adds a key, label and value to this matrix, returning a new matrix.
* <p>
* The key is checked to see if it is in the original; if so, the value for that key is added. If the key is not present,
* the new key, label and value are attached to the end of the matrix. This method does not ignores the label - if there is a key already present but
* the labels do not match, then an exception is thrown.
* @param key The key to which a value is to be added
* @param label The label for the key
* @param value The value to add
* @param tolerance The tolerance
* @return The sum of the matrices
*/
public LabelledMatrix1D<S, T> add(final S key, final Object label, final double value, final T tolerance) {
return add(key, label, value, tolerance, false);
}
protected LabelledMatrix1D<S, T> add(final LabelledMatrix1D<S, T> other, final T tolerance, final boolean ignoreLabel) {
Validate.notNull(other, "labelled matrix");
final S[] otherKeys = other.getKeys();
final Object[] otherLabels = other.getLabels();
final double[] otherValues = other.getValues();
final S[] originalKeys = getKeys();
final Object[] originalLabels = getLabels();
final double[] originalValues = getValues();
final int m = originalKeys.length;
final int n = otherKeys.length;
int count = m + n;
final S[] newKeys = Arrays.copyOf(originalKeys, count);
final Object[] newLabels = Arrays.copyOf(originalLabels, count);
final double[] newValues = Arrays.copyOf(originalValues, count);
for (int i = 0; i < n; i++) {
final int index = binarySearchWithTolerance(originalKeys, otherKeys[i], tolerance);
if (index >= 0) {
if (!ignoreLabel && !originalLabels[index].equals(otherLabels[i])) {
throw new IllegalArgumentException("Have a value for " + otherKeys[i] + " but the label of the value to add (" + otherLabels[i] + ") did not match the original (" + originalLabels[index]
+ ")");
}
count--;
newValues[index] += otherValues[i];
} else {
final int j = i - n + count;
newKeys[j] = otherKeys[i];
newLabels[j] = otherLabels[i];
newValues[j] = otherValues[i];
}
}
return getMatrix(Arrays.copyOf(newKeys, count), Arrays.copyOf(newLabels, count), getLabelsTitle(), Arrays.copyOf(newValues, count), getValuesTitle());
}
protected LabelledMatrix1D<S, T> add(final S key, final Object label, final double value, final T tolerance, final boolean ignoreLabel) {
Validate.notNull(key, "key");
Validate.notNull(label, "label");
final S[] originalKeys = getKeys();
final Object[] originalLabels = getLabels();
final double[] originalValues = getValues();
final int n = originalKeys.length;
final int index = binarySearchWithTolerance(originalKeys, key, tolerance);
if (index >= 0) {
if (!ignoreLabel && !originalLabels[index].equals(label)) {
throw new IllegalArgumentException("Have a value for " + key + " but the label of the value to add (" + label + ") did not match the original (" + originalLabels[index] + ")");
}
final S[] newKeys = Arrays.copyOf(originalKeys, n);
final Object[] newLabels = Arrays.copyOf(originalLabels, n);
final double[] newValues = Arrays.copyOf(originalValues, n);
newValues[index] += value;
return getMatrix(newKeys, newLabels, getLabelsTitle(), newValues, getValuesTitle());
}
final S[] newKeys = Arrays.copyOf(originalKeys, n + 1);
final Object[] newLabels = Arrays.copyOf(originalLabels, n + 1);
final double[] newValues = Arrays.copyOf(originalValues, n + 1);
newKeys[n] = key;
newLabels[n] = label;
newValues[n] = value;
return getMatrix(newKeys, newLabels, newValues);
}
public T getDefaultTolerance() {
return _defaultTolerance;
}
/**
* Compares two keys and indicates whether the first would be considered less than, equal to or greater than the
* second.
*
* @param key1 the first key to compare, not null
* @param key2 the second key to compare, not null
* @param tolerance the tolerance for equality of the keys
* @return the value 0 if {@code key1} is equal to {@code key2}; a value less than 0 if {@code key1} is less than
* {@code key2}; and a value greater than 0 if {@code key1} is greater than {@code key2}.
*/
public abstract int compare(S key1, S key2, T tolerance);
/**
* Compares two keys using the default equality tolerance, and indicates whether the first would be considered less
* than, equal to or greater than the second.
*
* @param key1 the first key to compare, not null
* @param key2 the second key to compare, not null
* @return the value 0 if {@code key1} is equal to {@code key2}; a value less than 0 if {@code key1} is less than
* {@code key2}; and a value greater than 0 if {@code key1} is greater than {@code key2}.
*/
public int compare(final S key1, final S key2) {
return compare(key1, key2, getDefaultTolerance());
}
public abstract LabelledMatrix1D<S, T> getMatrix(S[] keys, Object[] labels, String labelsTitle, double[] values, String valuesTitle);
public abstract LabelledMatrix1D<S, T> getMatrix(S[] keys, Object[] labels, double[] values);
public abstract LabelledMatrix1D<S, T> getMatrix(S[] keys, double[] values);
private void quickSort() {
(new ArrayQuickSorter<S>(_keys) {
@Override
protected int compare(final S first, final S second) {
return LabelledMatrix1D.this.compare(first, second);
}
@Override
protected void swap(final int first, final int second) {
super.swap(first, second);
swap(_labels, first, second);
swap(_values, first, second);
}
}).sort();
}
protected int binarySearchWithTolerance(final S[] keys, final S key, final T tolerance) {
int low = 0;
int high = keys.length - 1;
while (low <= high) {
final int mid = (low + high) >>> 1;
final S midVal = keys[mid];
final int comparison = compare(key, midVal, tolerance);
if (comparison == 0) {
return mid;
} else if (comparison > 0) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -(low + 1);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(_keys);
result = prime * result + Arrays.hashCode(_labels);
result = prime * result + ((_labelsTitle == null) ? 0 : _labelsTitle.hashCode());
result = prime * result + Arrays.hashCode(_values);
result = prime * result + ((_valuesTitle == null) ? 0 : _valuesTitle.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final LabelledMatrix1D<?, ?> other = (LabelledMatrix1D<?, ?>) obj;
if (!Arrays.equals(_keys, other._keys)) {
return false;
}
if (!Arrays.equals(_labels, other._labels)) {
return false;
}
if (_labelsTitle == null) {
if (other._labelsTitle != null) {
return false;
}
} else if (!_labelsTitle.equals(other._labelsTitle)) {
return false;
}
if (!Arrays.equals(_values, other._values)) {
return false;
}
if (_valuesTitle == null) {
if (other._valuesTitle != null) {
return false;
}
} else if (!_valuesTitle.equals(other._valuesTitle)) {
return false;
}
return true;
}
public LabelledMatrix1D<S, T> divideBy(double amountToDivideBy) {
double[] values = new double[_values.length];
for (int i = 0; i < _keys.length; i++) {
double value = _values[i];
values[i] = value / amountToDivideBy;
}
return getMatrix(_keys, _labels, values);
}
}