Browse Source

Add a tested sliding window.

It provides a container that collects values with timestamps and
lets you access only the values in a certain "time window".
Lucas Stadler 12 years ago
parent
commit
600b8e374f

+ 85 - 0
java/src/main/java/lp/java/understanding/SlidingWindow.java

@ -0,0 +1,85 @@
1
package lp.java.understanding;
2
3
import java.util.ArrayList;
4
import java.util.List;
5
6
public class SlidingWindow<T> {
7
    private static final long NEVER = -1;
8
9
    private List<Long> currentTimestamps;
10
    private List<T> currentValues;
11
    private List<Long> newTimestamps;
12
    private List<T> newValues;
13
    private long windowStart;
14
    private long windowSize;
15
16
    public SlidingWindow(long windowSize) {
17
        currentTimestamps = new ArrayList<Long>();
18
        currentValues = new ArrayList<T>();
19
        newTimestamps = new ArrayList<Long>();
20
        newValues = new ArrayList<T>();
21
        windowStart = NEVER;
22
        this.windowSize = windowSize;
23
    }
24
25
    public List<T> getCurrentValues() {
26
        return currentValues;
27
    }
28
29
    public void addValue(T value, long timestamp) {
30
        if (windowStart == NEVER) {
31
            windowStart = timestamp;
32
        }
33
        if (isInWindow(timestamp)) {
34
            currentTimestamps.add(timestamp);
35
            currentValues.add(value);
36
        } else {
37
            newTimestamps.add(timestamp);
38
            newValues.add(value);
39
        }
40
    }
41
42
    public long getWindowStart() {
43
        return windowStart;
44
    }
45
46
    public boolean isInWindow(long timestamp) {
47
        return timestamp >= windowStart && timestamp < windowStart + windowSize;
48
    }
49
50
    public long getWindowSize() {
51
        return windowSize;
52
    }
53
54
    public long getWindowEnd() {
55
        return windowStart + windowSize;
56
    }
57
58
    public boolean canSlide() {
59
        return !newValues.isEmpty();
60
    }
61
62
    public void slide(long slideBy) {
63
        windowStart += slideBy;
64
        for (int i = 0; i < currentValues.size(); i++) {
65
            long timestamp = currentTimestamps.get(i);
66
            if (!isInWindow(timestamp)) {
67
                currentTimestamps.remove(i);
68
                currentValues.remove(i);
69
                i -= 1;
70
            }
71
        }
72
73
        for (int i = 0; i < newValues.size(); i++) {
74
            long timestamp = newTimestamps.get(i);
75
            if (isInWindow(timestamp)) {
76
                T value = newValues.get(i);
77
                currentTimestamps.add(timestamp);
78
                currentValues.add(value);
79
                newTimestamps.remove(i);
80
                newValues.remove(i);
81
                i -= 1;
82
            }
83
        }
84
    }
85
}

+ 92 - 0
java/src/test/java/lp/java/understanding/TestSlidingWindow.java

@ -0,0 +1,92 @@
1
package lp.java.understanding;
2
3
import static org.junit.Assert.*;
4
import org.junit.Before;
5
import org.junit.Test;
6
7
public class TestSlidingWindow {
8
    private SlidingWindow<Integer> window;
9
10
    @Before
11
    public void setUp() {
12
        window = new SlidingWindow<Integer>(10);
13
    }
14
15
    @Test
16
    public void initiallyTheSlidingWindowHoldsNoValues() {
17
        assertTrue(window.getCurrentValues().isEmpty());
18
    }
19
20
    @Test
21
    public void testIsInWindow() {
22
        window.addValue(0, 0);
23
        assertFalse("before window start", window.isInWindow(window.getWindowStart() - 1));
24
        assertTrue("at window start", window.isInWindow(window.getWindowStart()));
25
        assertTrue("after window start", window.isInWindow(window.getWindowStart() + 1));
26
        assertFalse("excluding window end", window.isInWindow(window.getWindowEnd()));
27
        assertFalse("after window end", window.isInWindow(window.getWindowEnd() + 10));
28
    }
29
30
    @Test
31
    public void addingAValueInTheCurrentWindowAddsItImmediately() {
32
        window.addValue(0, 0);
33
        window.addValue(1, window.getWindowStart());
34
        assertTrue(window.getCurrentValues().contains(1));
35
    }
36
37
    @Test
38
    public void addingAValueNotInTheCurrentWindowDoesntAddItImmediately() {
39
        window.addValue(0, 0);
40
        window.addValue(1, window.getWindowEnd());
41
        assertFalse(window.getCurrentValues().contains(1));
42
    }
43
44
    @Test
45
    public void theFirstAddedValueDeterminesTheWindowStart() {
46
        window.addValue(1, 42);
47
        assertEquals(42, window.getWindowStart());
48
    }
49
50
    @Test
51
    public void addingAValueOutsideOfTheCurrentWindowMakesItSlideable() {
52
        window.addValue(1, 0);
53
        assertFalse(window.canSlide());
54
        window.addValue(2, window.getWindowEnd());
55
        assertTrue(window.canSlide());
56
    }
57
58
    @Test
59
    public void slidingRemovesValuesNotInTheCurrentWindowAnymore() {
60
        addManyTimedValues(1, 0l, 2, 1l, 3, 5l, 4, 8l);
61
        assertWindowContains(1, 2, 3, 4);
62
        window.slide(2);
63
        assertWindowContains(3, 4);
64
    }
65
66
    private void addManyTimedValues(Object ...valuesWithTimestamps) {
67
        if (valuesWithTimestamps.length % 2 != 0) {
68
            throw new IllegalArgumentException("must pass even number of arguments");
69
        }
70
        for (int i = 0; i < valuesWithTimestamps.length; i += 2) {
71
            Integer value = (Integer) valuesWithTimestamps[i];
72
            Long timestamp = (Long) valuesWithTimestamps[i + 1];
73
            window.addValue(value, timestamp);
74
        }
75
    }
76
77
    @Test
78
    public void slidingAddsNewValuesFromTheNewWindow() {
79
        addManyTimedValues(1, 0l, 2, 1l, 3, 5l, 4, 8l, 5, 11l, 6, 20l);
80
        assertWindowContains(1, 2, 3, 4);
81
        window.slide(2);
82
        assertWindowContains(3, 4, 5);
83
        window.slide(10);
84
        assertWindowContains(6);
85
    }
86
87
    private void assertWindowContains(Integer ...values) {
88
        for (Integer value : values) {
89
            assertTrue("contains " + value, window.getCurrentValues().contains(value));
90
        }
91
    }
92
}