/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
package alma.acs.algorithms;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import alma.acs.util.IsoDateFormat;
/**
* Apache commons math etc do not seem to provide decent binning functionality
* (except for a possible abuse of class EmpiricalDistributionImpl).
* That's why we do it here.
* It's an ad-hoc implementation used for binning various parsed log data
* in post-mortem debugging. Not yet tested enough for important operational use.
*/
public class DataBinner
{
/**
* Class to represent a point in a time series of data.
* The binning will be done over time intervals.
* (Instead of time in milliseconds, any other "long" kind of data can be used for binning.)
* The <code>value</code> carries arbitrary data, which can later be
* retrieved as a list for a given bin interval.
* @param <T> The type of {@link #value}.
*/
public static class TimeValue<T extends Comparable<T>> implements Comparable<TimeValue<T>> {
public final long timeMillis;
public final T value;
public TimeValue(long timestamp, T value) {
this.timeMillis = timestamp;
this.value = value;
}
@Override
public String toString() {
return IsoDateFormat.formatDate(new Date(timeMillis)) + " " + value;
}
@Override
public int compareTo(TimeValue<T> other) {
if (this.timeMillis < other.timeMillis) return -1;
if (this.timeMillis > other.timeMillis) return 1;
return (this.value.compareTo(other.value));
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof TimeValue<?>)) return false;
TimeValue<?> other = (TimeValue<?>) obj;
return new EqualsBuilder()
.append(timeMillis, other.timeMillis)
.append(value, other.value)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder().
append(timeMillis).
append(value).
toHashCode();
}
}
/**
* Represents a binning (time) interval and the data mapped to it.
* @param <T>
*/
public static class BinnedTimeValues<T extends Comparable<T>> {
/**
* Center of binning interval.
*/
public final long timeMillis;
public final List<TimeValue<T>> binnedData;
BinnedTimeValues(long timeMillis, List<TimeValue<T>> binnedData) {
this.timeMillis = timeMillis;
this.binnedData = binnedData;
}
}
/**
* Distributes the <code>data</code> into binning intervals of equal sizes (<code>binningIntervalMillis</code>).
* @param <T> The type of data associated with a time.
* @param data
* @param binningIntervalMillis
* @return data mapped to bins.
*/
public <T extends Comparable<T>> List<BinnedTimeValues<T>> binTimedData(List<TimeValue<T>> data, int binningIntervalMillis) {
if (binningIntervalMillis <= 1 ||
((binningIntervalMillis % 1000 != 0) && ((1000 % (binningIntervalMillis % 1000)) != 0) )) {
throw new IllegalArgumentException("Bad binningIntervalMillis=" + binningIntervalMillis);
}
List<BinnedTimeValues<T>> ret = new ArrayList<BinnedTimeValues<T>>();
if (data != null && data.size() > 0) {
long t0 = floor(data.get(0).timeMillis, binningIntervalMillis);
long tBinFloor = t0; // floor time in ms is included in the bin interval.
long tCurrent = t0;
List<TimeValue<T>> currentBinData = new ArrayList<TimeValue<T>>();
for (Iterator<TimeValue<T>> dataIter = data.iterator(); dataIter.hasNext();) {
TimeValue<T> timeValue = dataIter.next();
// assert time ordered list
if (timeValue.timeMillis < tCurrent) {
throw new IllegalArgumentException("Expecting time-ordered list! Error at time " + IsoDateFormat.formatDate(new Date(timeValue.timeMillis)) );
}
tCurrent = timeValue.timeMillis;
// Leaving the current bin?
while (tCurrent >= tBinFloor + binningIntervalMillis) {
// store old bin data
BinnedTimeValues<T> binnedTimeValue = new BinnedTimeValues<T>(tBinFloor + binningIntervalMillis/2, currentBinData);
ret.add(binnedTimeValue);
// prepare next bin (possibly empty)
currentBinData = new ArrayList<TimeValue<T>>();
tBinFloor += binningIntervalMillis;
}
currentBinData.add(timeValue);
// last bin?
if (!dataIter.hasNext() && !currentBinData.isEmpty()) {
BinnedTimeValues<T> binnedTimeValue = new BinnedTimeValues<T>(tBinFloor + binningIntervalMillis/2, currentBinData);
ret.add(binnedTimeValue);
}
}
}
return ret;
}
public static long floor(long value, long multipleOf) {
return (value / multipleOf) * multipleOf;
}
}