/*
* @(#)Calculation.java
*
* Copyright 2010 Instituto Superior Tecnico
* Founding Authors: Luis Cruz, Nuno Ochoa, Paulo Abrantes
*
* https://fenix-ashes.ist.utl.pt/
*
* This file is part of the Expenditure Tracking Module.
*
* The Expenditure Tracking Module is free software: you can
* redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version
* 3 of the License, or (at your option) any later version.
*
* The Expenditure Tracking Module is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the Expenditure Tracking Module. If not, see <http://www.gnu.org/licenses/>.
*
*/
package pt.ist.expenditureTrackingSystem.util;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
/**
*
* @author Luis Cruz
*
*/
public class Calculation<C extends Comparable<C>> {
public static enum Operation {
SUM, AVERAGE, MEDIAN;
}
private final Operation[] operations = new Operation[Operation.values().length];
private final C[] catagories;
private List<BigDecimal>[] values = null;
private BigDecimal[] sum;
private long[] count;
public Calculation(final C[] catagories, final Operation... operations) {
if (catagories == null) {
throw new NullPointerException("categories.not.specified");
}
if (operations == null || operations.length == 0) {
throw new NullPointerException("operations.not.specified");
}
this.catagories = Arrays.copyOf(catagories, catagories.length);
for (final Operation operation : operations) {
this.operations[operation.ordinal()] = operation;
}
if (this.operations[Operation.SUM.ordinal()] != null || this.operations[Operation.AVERAGE.ordinal()] != null) {
sum = new BigDecimal[catagories.length];
final BigDecimal zero = new BigDecimal(0);
for (int i = 0; i < catagories.length; i++) {
sum[i] = zero;
}
}
if (this.operations[Operation.AVERAGE.ordinal()] != null) {
count = new long[catagories.length];
}
if (this.operations[Operation.MEDIAN.ordinal()] != null) {
values = new List[catagories.length];
for (int i = 0; i < catagories.length; i++) {
values[i] = new ArrayList<BigDecimal>();
}
}
}
public void registerValue(final C catagory, final BigDecimal value) {
final int index = getIndex(catagory);
if (operations[Operation.AVERAGE.ordinal()] != null) {
count[index]++;
sum[index] = sum[index].add(value);
} else if (operations[Operation.SUM.ordinal()] != null) {
sum[index] = sum[index].add(value);
}
if (operations[Operation.MEDIAN.ordinal()] != null) {
values[index].add(value);
}
}
private int getIndex(final C catagory) {
for (int i = 0; i < catagories.length; i++) {
if (catagory.compareTo(catagories[i]) == 0) {
return i;
}
}
return -1;
}
public SortedMap<C, BigDecimal> getResult(final Operation operation) {
final SortedMap<C, BigDecimal> result = new TreeMap<C, BigDecimal>();
for (int i = 0; i < catagories.length; i++) {
final C catagory = catagories[i];
final BigDecimal value;
if (operation == Operation.SUM) {
value = this.sum[i];
result.put(catagory, value);
} else if (operation == Operation.AVERAGE) {
final BigDecimal sum = this.sum[i];
final long count = this.count[i];
if (count > 0) {
value = sum.divide(new BigDecimal(count), RoundingMode.HALF_EVEN);
result.put(catagory, value);
}
} else if (operation == Operation.MEDIAN) {
if (values[i].size() > 0) {
Collections.sort(values[i]);
final double midPoint = 0.5 * values[i].size();
if (midPoint == (int) midPoint) {
final double nextPoint = midPoint - 1;
final BigDecimal value1 = values[i].get((int) midPoint);
final BigDecimal value2 = values[i].get((int) nextPoint);
final BigDecimal sum = value1.add(value2);
value = sum.divide(new BigDecimal(2));
} else {
final double index = midPoint - 0.5;
value = values[i].get((int) index);
}
result.put(catagory, value);
}
} else {
throw new NullPointerException("no.requested.operation");
}
}
return result;
}
public SortedMap<C, BigDecimal> getResultSum() {
return getResult(Operation.SUM);
}
public SortedMap<C, BigDecimal> getResultAverage() {
return getResult(Operation.AVERAGE);
}
public SortedMap<C, BigDecimal> getResultMedian() {
return getResult(Operation.MEDIAN);
}
public SortedMap<C, BigDecimal> getMins() {
final SortedMap<C, BigDecimal> result = new TreeMap<C, BigDecimal>();
for (int i = 0; i < catagories.length; i++) {
final C catagory = catagories[i];
if (values != null && values[i] != null && values[i].size() > 0) {
final BigDecimal min = Collections.min(values[i]);
result.put(catagory, min);
} else {
result.put(catagory, null);
}
}
return result;
}
public SortedMap<C, BigDecimal> getMaxs() {
final SortedMap<C, BigDecimal> result = new TreeMap<C, BigDecimal>();
for (int i = 0; i < catagories.length; i++) {
final C catagory = catagories[i];
if (values != null && values[i] != null && values[i].size() > 0) {
final BigDecimal min = Collections.max(values[i]);
result.put(catagory, min);
} else {
result.put(catagory, null);
}
}
return result;
}
}