Thanks everyone. Here is my Java implementation of the Deque-based algorithm (
import java.util.*;
/**
* Calculation is exclusive, i.e. immediately after adding an entry, the entry is not under consideration for min/max. <br>
*
* Algorith outline: <br>
* at every step: <br>
* <br>
* if (!Deque.Empty) and (Deque.Head.Index <= CurrentIndex - T) then <br>
* Deque.ExtractHead; <br>
* //Head is too old, it is leaving the window <br>
* <br>
* while (!Deque.Empty) and (Deque.Tail.Value > CurrentValue) do <br>
* Deque.ExtractTail; <br>
* //remove elements that have no chance to become minimum in the window <br>
* <br>
* Deque.AddTail(CurrentValue, CurrentIndex); <br>
* CurrentMin = Deque.Head.Value <br>
* //Head value is minimum in the current window <br>
*/
public class RollingMinMax {
private static class Entry {
final int index;
final double value;
private Entry(int index, double value) {
this.index = index;
this.value = value;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Entry{");
sb.append("index=").append(index);
sb.append(", value=").append(value);
sb.append('}');
return sb.toString();
}
}
private final ArrayDeque<Entry> _minQueue;
private final ArrayDeque<Entry> _maxQueue;
private final int _windowSizeMin;
private final int _windowSizeMax;
private int _count= 0;
private ArrayList<Double> _minHistory;
private ArrayList<Double> _maxHistory;
public RollingMinMax(int windowSizeMin, int windowSizeMax, boolean keepHistories) {
_windowSizeMin = windowSizeMin;
_windowSizeMax = windowSizeMax;
if (_windowSizeMin < 0 || _windowSizeMax < 0) {
throw new IllegalArgumentException("window size(s) too small");
}
_minQueue = new ArrayDeque<>(windowSizeMin);
_maxQueue = new ArrayDeque<>(windowSizeMax);
if (keepHistories) {
_minHistory = new ArrayList<>();
_maxHistory = new ArrayList<>();
}
}
public void add(double datum) {
final Entry entry = new Entry(_count, datum);
// remove entriesthat are too old for consideration
while (!_minQueue.isEmpty() && _minQueue.getFirst().index < _count - _windowSizeMin) {
_minQueue.removeFirst();
}
while (!_maxQueue.isEmpty() && _maxQueue.getFirst().index < _count - _windowSizeMax) {
_maxQueue.removeFirst();
}
// remove entries too small/large
while (!_minQueue.isEmpty() && _minQueue.getLast().value > datum) {
_minQueue.removeLast();
}
while (!_maxQueue.isEmpty() && _maxQueue.getLast().value < datum) {
_maxQueue.removeLast();
}
enqueueCurrent(entry);
++_count;
if (_minHistory != null) {
_minHistory.add(getMin());
_maxHistory.add(getMax());
}
}
private void enqueueCurrent(Entry entry) {
_minQueue.addLast(entry);
_maxQueue.addLast(entry);
}
/** Double.NaN returned if not enough data added yet to compute. */
public double getMin() {
if (_count <= _windowSizeMin) {
return Double.NaN;
}
double result = _minQueue.getFirst().value;
return result;
}
public double getMax() {
if (_count <= _windowSizeMax) {
return Double.NaN;
}
double result = _maxQueue.getFirst().value;
return result;
}
private ArrayList<Double> getMinHistory() {
return _minHistory;
}
private ArrayList<Double> getMaxHistory() {
return _maxHistory;
}
public String toString() {
return "RollingMinMax[" + _windowSizeMin + ", "+ _windowSizeMax + "] min = " + getMin() + ", max =" + getMax() + ", count = " + _count;
}
public static void main(String[] ignored) {
final ArrayList<Double> data = (ArrayList<Double>) Arrays.asList(
1d, 4d, 6d, 0d, 9d, 5d, 3d, 6d, 8d, 10d, 13d, 15d, 8d, 14d, 12d, 5d, 6d, 8d, 10d, 7d, 5d, 3d, 2d
);
RollingMinMax calculator = new RollingMinMax(4, 6, true);
System.out.println("calculator = " + calculator);
int index = 0;
for (double datum : data) {
calculator.add(datum);
System.out.println(index + "\t data=" + datum + ", min=" + calculator.getMin() + ", max=" +calculator.getMax());
++index;
}
}
}