java/com.sap.sse.common.test/src/com/sap/sse/common/test/KadaneExtremeSubarraysFinderTest.java
... ...
@@ -1,71 +0,0 @@
1
-package com.sap.sse.common.test;
2
-
3
-import static org.junit.jupiter.api.Assertions.assertEquals;
4
-
5
-import org.junit.jupiter.api.BeforeEach;
6
-import org.junit.jupiter.api.Test;
7
-
8
-import com.sap.sse.common.scalablevalue.KadaneExtremeSubarraysFinder;
9
-import com.sap.sse.common.scalablevalue.ScalableDouble;
10
-
11
-public class KadaneExtremeSubarraysFinderTest {
12
- private static final double EPSILON = 0.00000001;
13
- private KadaneExtremeSubarraysFinder<Double, Double, ScalableDouble> finder;
14
-
15
- @BeforeEach
16
- public void setUp() {
17
- finder = new KadaneExtremeSubarraysFinder<>();
18
- }
19
-
20
- @Test
21
- public void testSimplePositiveSequence() {
22
- finder.add(new ScalableDouble(1));
23
- finder.add(new ScalableDouble(2));
24
- finder.add(new ScalableDouble(3));
25
- assertEquals(6.0, finder.getMaxSum().divide(1.0), EPSILON);
26
- assertEquals(0, finder.getStartIndexOfMaxSumSequence());
27
- assertEquals(2, finder.getEndIndexOfMaxSumSequence());
28
- }
29
-
30
- @Test
31
- public void testSimplePositiveSequenceWithInsertInTheMiddle() {
32
- finder.add(new ScalableDouble(1));
33
- finder.add(new ScalableDouble(3));
34
- finder.add(1, new ScalableDouble(2));
35
- assertEquals(6.0, finder.getMaxSum().divide(1.0), EPSILON);
36
- assertEquals(0, finder.getStartIndexOfMaxSumSequence());
37
- assertEquals(2, finder.getEndIndexOfMaxSumSequence());
38
- }
39
-
40
- @Test
41
- public void testSimpleSequenceWithPositiveAndNegative() {
42
- finder.add(new ScalableDouble(1));
43
- finder.add(new ScalableDouble(2));
44
- finder.add(new ScalableDouble(3));
45
- finder.add(new ScalableDouble(-4));
46
- finder.add(new ScalableDouble(5));
47
- finder.add(new ScalableDouble(6));
48
- finder.add(new ScalableDouble(-5));
49
- assertEquals(13.0, finder.getMaxSum().divide(1.0), EPSILON);
50
- assertEquals(0, finder.getStartIndexOfMaxSumSequence());
51
- assertEquals(5, finder.getEndIndexOfMaxSumSequence());
52
- }
53
-
54
- @Test
55
- public void testSimplePositiveSequenceWithLaterNegativeInsertInTheMiddle() {
56
- finder.add(new ScalableDouble(1));
57
- finder.add(new ScalableDouble(2));
58
- finder.add(new ScalableDouble(3));
59
- finder.add(new ScalableDouble(4));
60
- finder.add(new ScalableDouble(5));
61
- assertEquals(15.0, finder.getMaxSum().divide(1.0), EPSILON);
62
- assertEquals(0, finder.getStartIndexOfMaxSumSequence());
63
- assertEquals(4, finder.getEndIndexOfMaxSumSequence());
64
- finder.add(3, new ScalableDouble(-7));
65
- assertEquals(9.0, finder.getMaxSum().divide(1.0), EPSILON); // FIXME this test passes "coincidentally" because of the way getMaxSum() works while updating the aggregates...
66
- assertEquals(4, finder.getStartIndexOfMaxSumSequence());
67
- assertEquals(5, finder.getEndIndexOfMaxSumSequence());
68
- }
69
-
70
- // TODO add tests using the remove(...) method
71
-}
java/com.sap.sse.common.test/src/com/sap/sse/common/test/KadaneExtremeSubsequenceFinderTest.java
... ...
@@ -0,0 +1,72 @@
1
+package com.sap.sse.common.test;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
4
+
5
+import org.junit.jupiter.api.BeforeEach;
6
+import org.junit.jupiter.api.Test;
7
+
8
+import com.sap.sse.common.scalablevalue.KadaneExtremeSubsequenceFinder;
9
+import com.sap.sse.common.scalablevalue.KadaneExtremeSubsequenceFinderLinkedListImpl;
10
+import com.sap.sse.common.scalablevalue.ScalableDouble;
11
+
12
+public class KadaneExtremeSubsequenceFinderTest {
13
+ private static final double EPSILON = 0.00000001;
14
+ private KadaneExtremeSubsequenceFinder<Double, Double, ScalableDouble> finder;
15
+
16
+ @BeforeEach
17
+ public void setUp() {
18
+ finder = new KadaneExtremeSubsequenceFinderLinkedListImpl<>();
19
+ }
20
+
21
+ @Test
22
+ public void testSimplePositiveSequence() {
23
+ finder.add(new ScalableDouble(1));
24
+ finder.add(new ScalableDouble(2));
25
+ finder.add(new ScalableDouble(3));
26
+ assertEquals(6.0, finder.getMaxSum().divide(1.0), EPSILON);
27
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
28
+ assertEquals(2, finder.getEndIndexOfMaxSumSequence());
29
+ }
30
+
31
+ @Test
32
+ public void testSimplePositiveSequenceWithInsertInTheMiddle() {
33
+ finder.add(new ScalableDouble(1));
34
+ finder.add(new ScalableDouble(3));
35
+ finder.add(1, new ScalableDouble(2));
36
+ assertEquals(6.0, finder.getMaxSum().divide(1.0), EPSILON);
37
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
38
+ assertEquals(2, finder.getEndIndexOfMaxSumSequence());
39
+ }
40
+
41
+ @Test
42
+ public void testSimpleSequenceWithPositiveAndNegative() {
43
+ finder.add(new ScalableDouble(1));
44
+ finder.add(new ScalableDouble(2));
45
+ finder.add(new ScalableDouble(3));
46
+ finder.add(new ScalableDouble(-4));
47
+ finder.add(new ScalableDouble(5));
48
+ finder.add(new ScalableDouble(6));
49
+ finder.add(new ScalableDouble(-5));
50
+ assertEquals(13.0, finder.getMaxSum().divide(1.0), EPSILON);
51
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
52
+ assertEquals(5, finder.getEndIndexOfMaxSumSequence());
53
+ }
54
+
55
+ @Test
56
+ public void testSimplePositiveSequenceWithLaterNegativeInsertInTheMiddle() {
57
+ finder.add(new ScalableDouble(1));
58
+ finder.add(new ScalableDouble(2));
59
+ finder.add(new ScalableDouble(3));
60
+ finder.add(new ScalableDouble(4));
61
+ finder.add(new ScalableDouble(5));
62
+ assertEquals(15.0, finder.getMaxSum().divide(1.0), EPSILON);
63
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
64
+ assertEquals(4, finder.getEndIndexOfMaxSumSequence());
65
+ finder.add(3, new ScalableDouble(-7));
66
+ assertEquals(9.0, finder.getMaxSum().divide(1.0), EPSILON); // FIXME this test passes "coincidentally" because of the way getMaxSum() works while updating the aggregates...
67
+ assertEquals(4, finder.getStartIndexOfMaxSumSequence());
68
+ assertEquals(5, finder.getEndIndexOfMaxSumSequence());
69
+ }
70
+
71
+ // TODO add tests using the remove(...) method
72
+}
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/KadaneExtremeSubarraysFinder.java
... ...
@@ -1,273 +0,0 @@
1
-package com.sap.sse.common.scalablevalue;
2
-
3
-import java.io.Serializable;
4
-import java.util.Iterator;
5
-import java.util.LinkedList;
6
-import java.util.List;
7
-import java.util.ListIterator;
8
-
9
-/**
10
- * In a sequence of {@link ComparableScalableValueWithDistance} objects, tells the contiguous sub-sequence with the
11
- * greatest and the contiguous sub-sequence with the least sum, according to the
12
- * {@link ComparableScalableValueWithDistance#add(ScalableValue) add} and the
13
- * {@link ComparableScalableValueWithDistance#compareTo(Object) compareTo} methods.
14
- * <p>
15
- *
16
- * The sequence is mutable. In particular, elements can be added at any position, also before the start or after the
17
- * end, and elements can be removed at least from the beginning of the sequence. Updating the sub-sequences with minimal
18
- * and maximal sums happens with complexity O(1) when adding to the end of the sequence, so with constant effort
19
- * regardless the size of the sequence. When inserting into or removing from the sequence at arbitrary positions,
20
- * constant effort can no longer be guaranteed as changes may need to get propagated onwards to following elements.
21
- * <p>
22
- *
23
- * See also <a href="https://en.wikipedia.org/wiki/Maximum_subarray_problem">here</a> for a description of the
24
- * algorithm.
25
- *
26
- * @author Axel Uhl (d043530)
27
- *
28
- */
29
-public class KadaneExtremeSubarraysFinder<ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>>
30
-implements Serializable, Iterable<T> {
31
- private static final long serialVersionUID = 2109193559337714286L;
32
-
33
- /**
34
- * The elements constituting the full sequence in which to find the contiguous sub-sequences
35
- */
36
- private final List<T> sequence;
37
-
38
- /**
39
- * The element at index <tt>i</tt> holds the maximum value of the sum of any contiguous sub-sequence ending at index
40
- * <tt>i</tt>. Outside of {@code synchronized} blocks it holds as many elements as {@link #sequence}. The element
41
- * at index {@code i} is computed as {@code maxSumEndingAt.get(i-1)+sequence.get(i), max(sequence.get(i))}. This covers
42
- * the two cases extending the complete induction. Either, the sequence with the maximum sum ending at index {@code i}
43
- * includes prior elements; or the single element {@code sequence.get(i)} is greater than the sum of it and the maximum
44
- * sum ending at the previous element {@code i-1}.
45
- */
46
- private final List<ScalableValueWithDistance<ValueType, AveragesTo>> maxSumEndingAt;
47
-
48
- /**
49
- * Indices of the first element in {@link #sequence} of the contiguous sub-sequence having the maximum sum
50
- * ending at the {@link #sequence} index that corresponds with the index of the element in this list. Example:
51
- * if the contiguous sub-sequence in {@link #sequence} ending at the element before index 5 with the maximum
52
- * sum starts at index 1, then {@link #startIndexOfMaxSumSequence}{@code .get(5)==1}.
53
- */
54
- private List<Integer> startIndexOfMaxSumSequence;
55
-
56
- /**
57
- * Index of the element in {@link #sequence} at which the contiguous sub-sequence ends that has the maximum sum
58
- * of all such sub-sequences. This means that {@link #maxSumEndingAt}{@code .get(}{@link #endIndexOfMaxSumSequence})
59
- * is minimal across all elements in {@link #maxSumEndingAt}.
60
- */
61
- private int endIndexOfMaxSumSequence;
62
-
63
- /**
64
- * See {@code #maxSumEndingAt}, only for the minimum.
65
- */
66
- private final List<ScalableValueWithDistance<ValueType, AveragesTo>> minSumEndingAt;
67
-
68
- /**
69
- * Indices of the first element in {@link #sequence} of the contiguous sub-sequence having the minimum sum
70
- * ending at the {@link #sequence} index that corresponds with the index of the element in this list. Example:
71
- * if the contiguous sub-sequence in {@link #sequence} ending at the element before index 5 with the minimum
72
- * sum starts at index 1, then {@link #startIndexOfMaxSumSequence}{@code .get(5)==1}.
73
- */
74
- private List<Integer> startIndexOfMinSumSequence;
75
-
76
- /**
77
- * Index of the element in {@link #sequence} at which the contiguous sub-sequence ends that has the minimum sum
78
- * of all such sub-sequences. This means that {@link #minSumEndingAt}{@code .get(}{@link #endIndexOfMinSumSequence})
79
- * is minimal across all elements in {@link #minSumEndingAt}.
80
- */
81
- private int endIndexOfMinSumSequence;
82
-
83
- public KadaneExtremeSubarraysFinder() {
84
- sequence = new LinkedList<>();
85
- maxSumEndingAt = new LinkedList<>();
86
- minSumEndingAt = new LinkedList<>();
87
- startIndexOfMaxSumSequence = new LinkedList<>();
88
- endIndexOfMaxSumSequence = -1;
89
- startIndexOfMinSumSequence = new LinkedList<>();
90
- endIndexOfMinSumSequence = -1;
91
- }
92
-
93
- public synchronized void add(int index, T t) {
94
- final ScalableValueWithDistance<ValueType, AveragesTo> oldMaxSum = getMaxSum();
95
- final ScalableValueWithDistance<ValueType, AveragesTo> oldMinSum = getMinSum();
96
- final boolean insertingIntoMaxSumSequence = index <= endIndexOfMaxSumSequence && index > startIndexOfMaxSumSequence.get(endIndexOfMaxSumSequence);
97
- final boolean insertingIntoMinSumSequence = index <= endIndexOfMinSumSequence && index > startIndexOfMinSumSequence.get(endIndexOfMinSumSequence);
98
- sequence.add(index, t);
99
- final ScalableValueWithDistance<ValueType, AveragesTo> newMaxSumEndingAtIndex;
100
- final ScalableValueWithDistance<ValueType, AveragesTo> sumWithMax = index == 0 ? null : t.add(maxSumEndingAt.get(index-1));
101
- if (index == 0 || compare(t, sumWithMax) >= 0) {
102
- newMaxSumEndingAtIndex = t; // one-element sum consisting of element at "index" is the maximum
103
- startIndexOfMaxSumSequence.add(index, index);
104
- } else {
105
- newMaxSumEndingAtIndex = sumWithMax;
106
- startIndexOfMaxSumSequence.add(index, startIndexOfMaxSumSequence.get(index-1));
107
- }
108
- maxSumEndingAt.add(index, newMaxSumEndingAtIndex);
109
- if (oldMaxSum == null || compare(newMaxSumEndingAtIndex, oldMaxSum) > 0) {
110
- endIndexOfMaxSumSequence = index;
111
- }
112
- final ScalableValueWithDistance<ValueType, AveragesTo> newMinSumEndingAtIndex;
113
- final ScalableValueWithDistance<ValueType, AveragesTo> sumWithMin = index == 0 ? null : t.add(minSumEndingAt.get(index-1));
114
- if (index == 0 || compare(t, sumWithMin) <= 0) {
115
- newMinSumEndingAtIndex = t; // one-element sum consisting of element at "index" is the minimum
116
- startIndexOfMinSumSequence.add(index, index);
117
- } else {
118
- newMinSumEndingAtIndex = sumWithMin;
119
- startIndexOfMinSumSequence.add(index, startIndexOfMinSumSequence.get(index-1));
120
- }
121
- minSumEndingAt.add(index, newMinSumEndingAtIndex);
122
- if (oldMinSum == null || compare(newMinSumEndingAtIndex, oldMinSum) < 0) {
123
- endIndexOfMinSumSequence = index;
124
- }
125
- if (index < sequence.size()) {
126
- update(index+1, newMaxSumEndingAtIndex, newMinSumEndingAtIndex);
127
- }
128
- if (insertingIntoMaxSumSequence) { // TODO probably also check whether a "positive" value was inserted; in this case, max can only grow further, and sub-sequence indices will stay unchanged
129
- updateMax();
130
- }
131
- if (insertingIntoMinSumSequence) { // TODO probably also check whether a "negative" value was inserted; in this case, min can only shrink further, and sub-sequence indices will stay unchanged
132
- updateMin();
133
- }
134
- }
135
-
136
- private void updateMin() {
137
- // TODO Auto-generated method stub
138
-
139
- }
140
-
141
- private void updateMax() {
142
- // TODO Auto-generated method stub
143
-
144
- }
145
-
146
- private int compare(final ScalableValueWithDistance<ValueType, AveragesTo> a, final ScalableValueWithDistance<ValueType, AveragesTo> b) {
147
- return a.divide(1).compareTo(b.divide(1));
148
- }
149
-
150
- /**
151
- * For each element in {@link #sequence} starting at index {@code i}, this method checks whether the {@link #maxSumEndingAt}{@code [i]}
152
- * still is the maximum of {@link #maxSumEndingAt}{@code [i-1]+sequence[i]} and {@link #sequence}{@code [i]}. If yes, any change to
153
- * elements with index less than {@code i} do not have to be carried forward any further. Otherwise, {@link #maxSumEndingAt}{@code [i]}
154
- * is updated, and the process continues at {@code i+1} "recursively" (implemented iteratively, without recursion).
155
- */
156
- private void update(int i, ScalableValueWithDistance<ValueType, AveragesTo> maxSumEndingAtPreviousIndex, ScalableValueWithDistance<ValueType, AveragesTo> newMinSumEndingAtIndex) {
157
- final ListIterator<T> sequenceIter = sequence.listIterator(i);
158
- final ListIterator<ScalableValueWithDistance<ValueType, AveragesTo>> maxSumEndingAtIter = maxSumEndingAt.listIterator(i);
159
- final ListIterator<ScalableValueWithDistance<ValueType, AveragesTo>> minSumEndingAtIter = minSumEndingAt.listIterator(i);
160
- final ListIterator<Integer> startIndexOfMaxSumSequenceIter = startIndexOfMaxSumSequence.listIterator(i);
161
- final ListIterator<Integer> startIndexOfMinSumSequenceIter = startIndexOfMinSumSequence.listIterator(i);
162
- boolean finishedMax = false;
163
- boolean finishedMin = false;
164
- while (sequenceIter.hasNext() && (!finishedMax || !finishedMin)) {
165
- final T next = sequenceIter.next();
166
- if (!finishedMax) {
167
- final ScalableValueWithDistance<ValueType, AveragesTo> nextMaxSumEndingAt = maxSumEndingAtIter.next();
168
- startIndexOfMaxSumSequenceIter.next();
169
- final ScalableValueWithDistance<ValueType, AveragesTo> sum = next.add(maxSumEndingAtPreviousIndex);
170
- final boolean nextGreaterOrEqualsThanSum = compare(next, sum) >= 0;
171
- final ScalableValueWithDistance<ValueType, AveragesTo> newMaxSumEndingAt = nextGreaterOrEqualsThanSum ? next : sum;
172
- if (compare(nextMaxSumEndingAt, newMaxSumEndingAt) != 0) {
173
- maxSumEndingAtIter.remove();
174
- maxSumEndingAtIter.add(newMaxSumEndingAt);
175
- maxSumEndingAtPreviousIndex = newMaxSumEndingAt;
176
- startIndexOfMaxSumSequenceIter.remove();
177
- startIndexOfMaxSumSequenceIter.add(nextGreaterOrEqualsThanSum ? i : startIndexOfMaxSumSequence.get(i-1));
178
- if (compare(newMaxSumEndingAt, getMaxSum()) > 0) { // FIXME getMaxSum() cannot be asked while we are still updating; indices may have moved left (remove) or right (add)!
179
- endIndexOfMaxSumSequence = i;
180
- }
181
- } else {
182
- finishedMax = true; // no more changes to propagate
183
- }
184
- }
185
- if (!finishedMin) {
186
- final ScalableValueWithDistance<ValueType, AveragesTo> nextMinSumEndingAt = minSumEndingAtIter.next();
187
- startIndexOfMinSumSequenceIter.next();
188
- final ScalableValueWithDistance<ValueType, AveragesTo> sum = next.add(maxSumEndingAtPreviousIndex);
189
- final boolean nextLessOrEqualsThanSum = compare(next, sum) <= 0;
190
- final ScalableValueWithDistance<ValueType, AveragesTo> newMinSumEndingAt = nextLessOrEqualsThanSum ? next : sum;
191
- if (compare(nextMinSumEndingAt, newMinSumEndingAt) != 0) {
192
- minSumEndingAtIter.remove();
193
- minSumEndingAtIter.add(newMinSumEndingAt);
194
- maxSumEndingAtPreviousIndex = newMinSumEndingAt;
195
- startIndexOfMinSumSequenceIter.remove();
196
- startIndexOfMinSumSequenceIter.add(nextLessOrEqualsThanSum ? i : startIndexOfMinSumSequence.get(i-1));
197
- if (compare(newMinSumEndingAt, getMinSum()) < 0) {
198
- endIndexOfMinSumSequence = i;
199
- }
200
- } else {
201
- finishedMin = true; // no more changes to propagate
202
- }
203
- }
204
- i++;
205
- }
206
- }
207
-
208
- public synchronized void remove(int index) {
209
- sequence.remove(index);
210
- final ScalableValueWithDistance<ValueType, AveragesTo> maxSumEndingAtIndex = maxSumEndingAt.remove(index);
211
- startIndexOfMaxSumSequence.remove(index);
212
- if (endIndexOfMaxSumSequence > index) {
213
- endIndexOfMaxSumSequence--;
214
- } else if (endIndexOfMaxSumSequence == index) {
215
- // TODO but if endIndexOfMaxSumSequence == index, we have deleted the last element of the max sum sequence and need to re-evaluate
216
- }
217
- if (endIndexOfMinSumSequence > index) {
218
- endIndexOfMinSumSequence--;
219
- } else if (endIndexOfMinSumSequence == index) {
220
- // TODO but if endIndexOfMinSumSequence == index, we have deleted the last element of the min sum sequence and need to re-evaluate
221
- }
222
- final ScalableValueWithDistance<ValueType, AveragesTo> minSumEndingAtIndex = minSumEndingAt.remove(index);
223
- startIndexOfMinSumSequence.remove(index);
224
- update(index+1, maxSumEndingAtIndex, minSumEndingAtIndex);
225
- }
226
-
227
- public synchronized void add(T t) {
228
- add(sequence.size(), t);
229
- }
230
-
231
- public synchronized void remove(T t) {
232
- remove(sequence.indexOf(t));
233
- }
234
-
235
- public ScalableValueWithDistance<ValueType, AveragesTo> getMaxSum() {
236
- return endIndexOfMaxSumSequence == -1 ? null : maxSumEndingAt.get(endIndexOfMaxSumSequence);
237
- }
238
-
239
- public ScalableValueWithDistance<ValueType, AveragesTo> getMinSum() {
240
- return endIndexOfMinSumSequence == -1 ? null : minSumEndingAt.get(endIndexOfMinSumSequence);
241
- }
242
-
243
- public int getStartIndexOfMaxSumSequence() {
244
- return startIndexOfMaxSumSequence.isEmpty() ? -1 : startIndexOfMaxSumSequence.get(endIndexOfMaxSumSequence);
245
- }
246
-
247
- /**
248
- * @return the index into {@link #sequence} holding the last element of the contiguous sub-sequence that has the
249
- * maximal sum; note that pointing <em>to</em> and not <em>after</em> the last element of that sequence is
250
- * slightly different from how indices may be handled in some other from/to collection operations.
251
- */
252
- public int getEndIndexOfMaxSumSequence() {
253
- return endIndexOfMaxSumSequence;
254
- }
255
-
256
- public int getStartIndexOfMinSumSequence() {
257
- return startIndexOfMinSumSequence.isEmpty() ? -1 : startIndexOfMinSumSequence.get(endIndexOfMinSumSequence);
258
- }
259
-
260
- /**
261
- * @return the index into {@link #sequence} holding the last element of the contiguous sub-sequence that has the
262
- * minimal sum; note that pointing <em>to</em> and not <em>after</em> the last element of that sequence is
263
- * slightly different from how indices may be handled in some other from/to collection operations.
264
- */
265
- public int getEndIndexOfMinSumSequence() {
266
- return endIndexOfMinSumSequence;
267
- }
268
-
269
- @Override
270
- public Iterator<T> iterator() {
271
- return sequence.iterator();
272
- }
273
-}
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/KadaneExtremeSubsequenceFinder.java
... ...
@@ -0,0 +1,69 @@
1
+package com.sap.sse.common.scalablevalue;
2
+
3
+import java.io.Serializable;
4
+import java.util.Iterator;
5
+
6
+/**
7
+ * In a sequence of {@link ComparableScalableValueWithDistance} objects, tells the contiguous sub-sequence with the
8
+ * greatest and the contiguous sub-sequence with the least sum, according to the
9
+ * {@link ComparableScalableValueWithDistance#add(ScalableValue) add} and the
10
+ * {@link ComparableScalableValueWithDistance#compareTo(Object) compareTo} methods.
11
+ * <p>
12
+ *
13
+ * The sequence is mutable. In particular, elements can be added at any position, also before the start or after the
14
+ * end, and elements can be removed at least from the beginning of the sequence. Updating the sub-sequences with minimal
15
+ * and maximal sums happens with complexity O(1) when adding to the end of the sequence, so with constant effort
16
+ * regardless the size of the sequence. When inserting into or removing from the sequence at arbitrary positions,
17
+ * constant effort can no longer be guaranteed as changes may need to get propagated onwards to following elements.
18
+ * <p>
19
+ *
20
+ * See also <a href="https://en.wikipedia.org/wiki/Maximum_subarray_problem">here</a> for a description of the
21
+ * algorithm.
22
+ *
23
+ * @author Axel Uhl (d043530)
24
+ *
25
+ */
26
+public interface KadaneExtremeSubsequenceFinder<ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>>
27
+ extends Serializable, Iterable<T> {
28
+ int size();
29
+
30
+ default boolean isEmpty() {
31
+ return size() == 0;
32
+ }
33
+
34
+ void add(int index, T t);
35
+
36
+ default void add(T t) {
37
+ add(size(), t);
38
+ }
39
+
40
+ void remove(int index);
41
+
42
+ void remove(T t);
43
+
44
+ ScalableValueWithDistance<ValueType, AveragesTo> getMinSum();
45
+
46
+ ScalableValueWithDistance<ValueType, AveragesTo> getMaxSum();
47
+
48
+ Iterator<T> getSubSequenceWithMaxSum();
49
+
50
+ Iterator<T> getSubSequenceWithMinSum();
51
+
52
+ int getStartIndexOfMaxSumSequence();
53
+
54
+ /**
55
+ * @return the index into {@link #sequence} holding the last element of the contiguous sub-sequence that has the
56
+ * maximal sum; note that pointing <em>to</em> and not <em>after</em> the last element of that sequence is
57
+ * slightly different from how indices may be handled in some other from/to collection operations.
58
+ */
59
+ int getEndIndexOfMaxSumSequence();
60
+
61
+ int getStartIndexOfMinSumSequence();
62
+
63
+ /**
64
+ * @return the index into {@link #sequence} holding the last element of the contiguous sub-sequence that has the
65
+ * minimal sum; note that pointing <em>to</em> and not <em>after</em> the last element of that sequence is
66
+ * slightly different from how indices may be handled in some other from/to collection operations.
67
+ */
68
+ int getEndIndexOfMinSumSequence();
69
+}
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/KadaneExtremeSubsequenceFinderImpl.java
... ...
@@ -0,0 +1,349 @@
1
+package com.sap.sse.common.scalablevalue;
2
+
3
+import java.util.Iterator;
4
+import java.util.function.BiFunction;
5
+import java.util.function.Consumer;
6
+import java.util.function.Function;
7
+
8
+public class KadaneExtremeSubsequenceFinderImpl<ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>>
9
+ implements KadaneExtremeSubsequenceFinder<ValueType, AveragesTo, T> {
10
+
11
+ private static final long serialVersionUID = -8986609116472739636L;
12
+
13
+ private Node<ValueType, AveragesTo, T> first;
14
+
15
+ private Node<ValueType, AveragesTo, T> last;
16
+
17
+ private int size;
18
+
19
+ /**
20
+ * Nodes of this type are used to construct a doubly-linked list, with each node holding a reference to the node
21
+ * forming the start of the sequence with the maximum sum.
22
+ *
23
+ * @author Axel Uhl (d043530)
24
+ */
25
+ private static class Node<ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>> {
26
+ private final T value;
27
+ private Node<ValueType, AveragesTo, T> previous;
28
+ private Node<ValueType, AveragesTo, T> next;
29
+ private ScalableValueWithDistance<ValueType, AveragesTo> minSumEndingHere;
30
+ private Node<ValueType, AveragesTo, T> startOfMinSumSubSequenceEndingHere;
31
+ private ScalableValueWithDistance<ValueType, AveragesTo> maxSumEndingHere;
32
+ private Node<ValueType, AveragesTo, T> startOfMaxSumSubSequenceEndingHere;
33
+
34
+ private Node(Node<ValueType, AveragesTo, T> previous, Node<ValueType, AveragesTo, T> next, T value) {
35
+ super();
36
+ this.previous = previous;
37
+ this.next = next;
38
+ this.value = value;
39
+ this.minSumEndingHere = null;
40
+ this.startOfMinSumSubSequenceEndingHere = null;
41
+ this.maxSumEndingHere = null;
42
+ this.startOfMaxSumSubSequenceEndingHere = null;
43
+ }
44
+
45
+ private Node<ValueType, AveragesTo, T> getPrevious() {
46
+ return previous;
47
+ }
48
+
49
+ private Node<ValueType, AveragesTo, T> getNext() {
50
+ return next;
51
+ }
52
+
53
+ private void setPrevious(Node<ValueType, AveragesTo, T> previous) {
54
+ this.previous = previous;
55
+ }
56
+
57
+ private void setNext(Node<ValueType, AveragesTo, T> next) {
58
+ this.next = next;
59
+ }
60
+
61
+ private T getValue() {
62
+ return value;
63
+ }
64
+
65
+ private ScalableValueWithDistance<ValueType, AveragesTo> getMinSumEndingHere() {
66
+ return minSumEndingHere;
67
+ }
68
+
69
+ private Node<ValueType, AveragesTo, T> getStartOfMinSumSubSequenceEndingHere() {
70
+ return startOfMinSumSubSequenceEndingHere;
71
+ }
72
+
73
+ private void setMinSumEndingHere(ScalableValueWithDistance<ValueType, AveragesTo> minSumEndingHere) {
74
+ this.minSumEndingHere = minSumEndingHere;
75
+ }
76
+
77
+ private void setStartOfMinSumSubSequenceEndingHere(Node<ValueType, AveragesTo, T> startOfMinSumSubSequenceEndingHere) {
78
+ this.startOfMinSumSubSequenceEndingHere = startOfMinSumSubSequenceEndingHere;
79
+ }
80
+
81
+ private void setMaxSumEndingHere(ScalableValueWithDistance<ValueType, AveragesTo> maxSumEndingHere) {
82
+ this.maxSumEndingHere = maxSumEndingHere;
83
+ }
84
+
85
+ private void setStartOfMaxSumSubSequenceEndingHere(Node<ValueType, AveragesTo, T> startOfMaxSumSubSequenceEndingHere) {
86
+ this.startOfMaxSumSubSequenceEndingHere = startOfMaxSumSubSequenceEndingHere;
87
+ }
88
+
89
+ private ScalableValueWithDistance<ValueType, AveragesTo> getMaxSumEndingHere() {
90
+ return maxSumEndingHere;
91
+ }
92
+
93
+ private Node<ValueType, AveragesTo, T> getStartOfMaxSumSubSequenceEndingHere() {
94
+ return startOfMaxSumSubSequenceEndingHere;
95
+ }
96
+
97
+ /**
98
+ * Updates this node's extreme sum values of the sub-sequences ending at this node, considering the values
99
+ * stored in the {@link #getPrevious() previous} element, if such an element exists. Furthermore, the
100
+ * references to the nodes where these sub-sequences start are updated accordingly.
101
+ *
102
+ * @return whether the node has changed during this update; this may mean a change in the min/max sum and/or the
103
+ * the start of the extreme sub-sequence(s) ending at this node. Any such change requires updating
104
+ * {@link #getNext() following nodes} too.
105
+ */
106
+ private boolean updateThisFromPrevious() {
107
+ final boolean changedByMax = updateMaxFromPrevious();
108
+ final boolean changedByMin = updateMinFromPrevious();
109
+ return changedByMax || changedByMin;
110
+ }
111
+
112
+ /**
113
+ * Updates this node's min sum values of the sub-sequences ending at this node, considering the values stored in
114
+ * the {@link #getPrevious() previous} element, if such an element exists. Furthermore, the references to the
115
+ * node where the min sum sub-sequence starts is updated accordingly.
116
+ *
117
+ * @return whether the node's min sum-related properties changed during this update; this may mean a change in
118
+ * the min sum and/or the the start of the min sum sub-sequence(s) ending at this node. Any such change
119
+ * requires updating {@link #getNext() following nodes} using this method, too. It does not happen
120
+ * automatically by calling this method. This method updates only this node.
121
+ */
122
+ private boolean updateMinFromPrevious() {
123
+ return updateThisFromPrevious(Node::getMinSumEndingHere,
124
+ Node::getStartOfMinSumSubSequenceEndingHere, this::setMinSumEndingHere,
125
+ this::setStartOfMinSumSubSequenceEndingHere, (a, b)->compare(b, a));
126
+ }
127
+
128
+ /**
129
+ * Updates this node's max sum values of the sub-sequences ending at this node, considering the values stored in
130
+ * the {@link #getPrevious() previous} element, if such an element exists. Furthermore, the references to the
131
+ * node where the max sum sub-sequence starts is updated accordingly.
132
+ *
133
+ * @return whether the node's max sum-related properties changed during this update; this may mean a change in
134
+ * the max sum and/or the the start of the max sum sub-sequence(s) ending at this node. Any such change
135
+ * requires updating {@link #getNext() following nodes} using this method, too. It does not happen
136
+ * automatically by calling this method. This method updates only this node.
137
+ */
138
+ private boolean updateMaxFromPrevious() {
139
+ return updateThisFromPrevious(Node::getMaxSumEndingHere,
140
+ Node::getStartOfMaxSumSubSequenceEndingHere, this::setMaxSumEndingHere,
141
+ this::setStartOfMaxSumSubSequenceEndingHere, this::compare);
142
+ }
143
+
144
+ private boolean updateThisFromPrevious(Function<Node<ValueType, AveragesTo, T>, ScalableValueWithDistance<ValueType, AveragesTo>> getExtremeSumEndingHere,
145
+ Function<Node<ValueType, AveragesTo, T>, Node<ValueType, AveragesTo, T>> getStartOfExtremeSumSubSequenceEndingHere,
146
+ Consumer<ScalableValueWithDistance<ValueType, AveragesTo>> setExtremeSumEndingHere,
147
+ Consumer<Node<ValueType, AveragesTo, T>> setStartOfExtremeSubSubSequenceEndingHere,
148
+ BiFunction<ScalableValueWithDistance<ValueType, AveragesTo>, ScalableValueWithDistance<ValueType, AveragesTo>, Integer> comparator) {
149
+ boolean changed = false;
150
+ final ScalableValueWithDistance<ValueType, AveragesTo> newMaxSumEndingAtIndex;
151
+ final Node<ValueType, AveragesTo, T> newStartOfMaxSumSubSequenceEndingHere;
152
+ final ScalableValueWithDistance<ValueType, AveragesTo> sumWithMax = getPrevious() == null ? null : getValue().add(getExtremeSumEndingHere.apply(getPrevious()));
153
+ if (getPrevious() == null || comparator.apply(getValue(), sumWithMax) >= 0) {
154
+ newMaxSumEndingAtIndex = getValue(); // one-element sum consisting of element at "index" is the maximum
155
+ newStartOfMaxSumSubSequenceEndingHere = this;
156
+ } else {
157
+ newMaxSumEndingAtIndex = sumWithMax;
158
+ newStartOfMaxSumSubSequenceEndingHere = getStartOfExtremeSumSubSequenceEndingHere.apply(getPrevious());
159
+ }
160
+ if (!newMaxSumEndingAtIndex.equals(getExtremeSumEndingHere.apply(this))) {
161
+ changed = true;
162
+ setExtremeSumEndingHere.accept(newMaxSumEndingAtIndex);
163
+ }
164
+ if (newStartOfMaxSumSubSequenceEndingHere != getStartOfExtremeSumSubSequenceEndingHere.apply(this)) {
165
+ changed = true;
166
+ setStartOfExtremeSubSubSequenceEndingHere.accept(newStartOfMaxSumSubSequenceEndingHere);
167
+ }
168
+ return changed;
169
+ }
170
+
171
+ private Integer compare(final ScalableValueWithDistance<ValueType, AveragesTo> a, final ScalableValueWithDistance<ValueType, AveragesTo> b) {
172
+ return a.divide(1).compareTo(b.divide(1));
173
+ }
174
+ }
175
+
176
+ public KadaneExtremeSubsequenceFinderImpl() {
177
+ this.size = 0;
178
+ this.first = null;
179
+ this.last = null;
180
+ }
181
+
182
+ @Override
183
+ public int size() {
184
+ return size;
185
+ }
186
+
187
+ @Override
188
+ public boolean isEmpty() {
189
+ return first == null;
190
+ }
191
+
192
+ @Override
193
+ public Iterator<T> iterator() {
194
+ return new Iterator<T>() {
195
+ private Node<ValueType, AveragesTo, T> current = first;
196
+ @Override
197
+ public boolean hasNext() {
198
+ return current != null;
199
+ }
200
+
201
+ @Override
202
+ public T next() {
203
+ final T result = current.getValue();
204
+ current = current.getNext();
205
+ return result;
206
+ }
207
+ };
208
+ }
209
+
210
+ @Override
211
+ public void add(int index, T t) {
212
+ if (index < 0 || index > size()) {
213
+ throw new IndexOutOfBoundsException("Trying to add at index "+index+" to a sequence of size "+size());
214
+ }
215
+ if (isEmpty()) {
216
+ final Node<ValueType, AveragesTo, T> node = new Node<>(/* previous */ null, /* next */ null, t);
217
+ first = node;
218
+ last = node;
219
+ node.updateThisFromPrevious(); // no need to consider changes; it's the only element
220
+ } else {
221
+ final Node<ValueType, AveragesTo, T> nodeBeforeIndex;
222
+ final Node<ValueType, AveragesTo, T> nodeAfterIndex;
223
+ if (index>0) {
224
+ nodeBeforeIndex = getNode(index-1);
225
+ nodeAfterIndex = nodeBeforeIndex.getNext();
226
+ } else if (index<size()) {
227
+ nodeAfterIndex = getNode(index);
228
+ nodeBeforeIndex = nodeAfterIndex.getPrevious();
229
+ } else {
230
+ throw new InternalError("This shouldn't have happened as it means the sequence is empty, and we shouldn't have arrived in this branch");
231
+ }
232
+ final Node<ValueType, AveragesTo, T> node = new Node<>(nodeBeforeIndex, nodeAfterIndex, t);
233
+ if (nodeBeforeIndex != null) {
234
+ nodeBeforeIndex.setNext(node);
235
+ }
236
+ if (nodeAfterIndex != null) {
237
+ nodeAfterIndex.setPrevious(node);
238
+ }
239
+ node.updateThisFromPrevious();
240
+ if (nodeBeforeIndex == null
241
+ || !node.getMaxSumEndingHere().equals(nodeBeforeIndex.getMaxSumEndingHere())
242
+ || !node.getMinSumEndingHere().equals(nodeBeforeIndex.getMinSumEndingHere())
243
+ || node.getStartOfMaxSumSubSequenceEndingHere() != nodeBeforeIndex.getStartOfMaxSumSubSequenceEndingHere()
244
+ || node.getStartOfMinSumSubSequenceEndingHere().equals(nodeBeforeIndex.getStartOfMinSumSubSequenceEndingHere())) {
245
+ // the inserted node differs from the previous node in one of the extreme sums ending at it, and/or
246
+ // regarding where those sub-sequences start, so we need to propagate the updates to subsequent nodes
247
+ propagateChanges(node);
248
+ }
249
+ }
250
+ }
251
+
252
+ private void propagateChanges(Node<ValueType, AveragesTo, T> node) {
253
+ Node<ValueType, AveragesTo, T> current = node.getNext();
254
+ boolean changedMin = true;
255
+ boolean changedMax = true;
256
+ while ((changedMin || changedMax) && current != null) {
257
+ if (changedMin) {
258
+ changedMin = current.updateMinFromPrevious();
259
+ }
260
+ if (changedMax) {
261
+ changedMax = current.updateMaxFromPrevious();
262
+ }
263
+ current = current.getNext();
264
+ }
265
+ }
266
+
267
+ Node<ValueType, AveragesTo, T> getNode(int index) {
268
+ final Node<ValueType, AveragesTo, T> result;
269
+ if (isEmpty()) {
270
+ result = null;
271
+ } else {
272
+ if (index > size()/2) { // search from the end
273
+ result = step(last, size()-1-index, Node::getPrevious);
274
+ } else { // search from the beginning
275
+ result = step(first, index, Node::getNext);
276
+ }
277
+ }
278
+ return result;
279
+ }
280
+
281
+ private Node<ValueType, AveragesTo, T> step(Node<ValueType, AveragesTo, T> start, int numberOfSteps,
282
+ Function<Node<ValueType, AveragesTo, T>, Node<ValueType, AveragesTo, T>> stepper) {
283
+ Node<ValueType, AveragesTo, T> current = start;
284
+ for (int i=0; i<numberOfSteps; i++) {
285
+ current = stepper.apply(current);
286
+ }
287
+ return current;
288
+ }
289
+
290
+ @Override
291
+ public void remove(int index) {
292
+ // TODO Auto-generated method stub
293
+
294
+ }
295
+
296
+ @Override
297
+ public void remove(T t) {
298
+ // TODO Auto-generated method stub
299
+
300
+ }
301
+
302
+ @Override
303
+ public ScalableValueWithDistance<ValueType, AveragesTo> getMinSum() {
304
+ // TODO Auto-generated method stub
305
+ return null;
306
+ }
307
+
308
+ @Override
309
+ public ScalableValueWithDistance<ValueType, AveragesTo> getMaxSum() {
310
+ // TODO Auto-generated method stub
311
+ return null;
312
+ }
313
+
314
+ @Override
315
+ public int getStartIndexOfMaxSumSequence() {
316
+ // TODO Auto-generated method stub
317
+ return 0;
318
+ }
319
+
320
+ @Override
321
+ public int getEndIndexOfMaxSumSequence() {
322
+ // TODO Auto-generated method stub
323
+ return 0;
324
+ }
325
+
326
+ @Override
327
+ public int getStartIndexOfMinSumSequence() {
328
+ // TODO Auto-generated method stub
329
+ return 0;
330
+ }
331
+
332
+ @Override
333
+ public int getEndIndexOfMinSumSequence() {
334
+ // TODO Auto-generated method stub
335
+ return 0;
336
+ }
337
+
338
+ @Override
339
+ public Iterator<T> getSubSequenceWithMaxSum() {
340
+ // TODO Auto-generated method stub
341
+ return null;
342
+ }
343
+
344
+ @Override
345
+ public Iterator<T> getSubSequenceWithMinSum() {
346
+ // TODO Auto-generated method stub
347
+ return null;
348
+ }
349
+}
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/KadaneExtremeSubsequenceFinderLinkedListImpl.java
... ...
@@ -0,0 +1,286 @@
1
+package com.sap.sse.common.scalablevalue;
2
+
3
+import java.io.Serializable;
4
+import java.util.Iterator;
5
+import java.util.LinkedList;
6
+import java.util.List;
7
+import java.util.ListIterator;
8
+
9
+/**
10
+ * This implementation uses {@link LinkedList}s to implement the full sequence and the max/min sum and start indices
11
+ * collection. This requires index manipulations when elements are inserted to or removed from anywhere but the end of
12
+ * the sequence.
13
+ *
14
+ * @author Axel Uhl (d043530)
15
+ *
16
+ */
17
+public class KadaneExtremeSubsequenceFinderLinkedListImpl<ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>>
18
+implements KadaneExtremeSubsequenceFinder<ValueType, AveragesTo, T>, Serializable, Iterable<T> {
19
+ private static final long serialVersionUID = 2109193559337714286L;
20
+
21
+ /**
22
+ * The elements constituting the full sequence in which to find the contiguous sub-sequences
23
+ */
24
+ private final List<T> sequence;
25
+
26
+ /**
27
+ * The element at index <tt>i</tt> holds the maximum value of the sum of any contiguous sub-sequence ending at index
28
+ * <tt>i</tt>. Outside of {@code synchronized} blocks it holds as many elements as {@link #sequence}. The element
29
+ * at index {@code i} is computed as {@code maxSumEndingAt.get(i-1)+sequence.get(i), max(sequence.get(i))}. This covers
30
+ * the two cases extending the complete induction. Either, the sequence with the maximum sum ending at index {@code i}
31
+ * includes prior elements; or the single element {@code sequence.get(i)} is greater than the sum of it and the maximum
32
+ * sum ending at the previous element {@code i-1}.
33
+ */
34
+ private final List<ScalableValueWithDistance<ValueType, AveragesTo>> maxSumEndingAt;
35
+
36
+ /**
37
+ * Indices of the first element in {@link #sequence} of the contiguous sub-sequence having the maximum sum
38
+ * ending at the {@link #sequence} index that corresponds with the index of the element in this list. Example:
39
+ * if the contiguous sub-sequence in {@link #sequence} ending at the element before index 5 with the maximum
40
+ * sum starts at index 1, then {@link #startIndexOfMaxSumSequence}{@code .get(5)==1}.
41
+ */
42
+ private List<Integer> startIndexOfMaxSumSequence;
43
+
44
+ /**
45
+ * Index of the element in {@link #sequence} at which the contiguous sub-sequence ends that has the maximum sum
46
+ * of all such sub-sequences. This means that {@link #maxSumEndingAt}{@code .get(}{@link #endIndexOfMaxSumSequence})
47
+ * is minimal across all elements in {@link #maxSumEndingAt}.
48
+ */
49
+ private int endIndexOfMaxSumSequence;
50
+
51
+ /**
52
+ * See {@code #maxSumEndingAt}, only for the minimum.
53
+ */
54
+ private final List<ScalableValueWithDistance<ValueType, AveragesTo>> minSumEndingAt;
55
+
56
+ /**
57
+ * Indices of the first element in {@link #sequence} of the contiguous sub-sequence having the minimum sum
58
+ * ending at the {@link #sequence} index that corresponds with the index of the element in this list. Example:
59
+ * if the contiguous sub-sequence in {@link #sequence} ending at the element before index 5 with the minimum
60
+ * sum starts at index 1, then {@link #startIndexOfMaxSumSequence}{@code .get(5)==1}.
61
+ */
62
+ private List<Integer> startIndexOfMinSumSequence;
63
+
64
+ /**
65
+ * Index of the element in {@link #sequence} at which the contiguous sub-sequence ends that has the minimum sum
66
+ * of all such sub-sequences. This means that {@link #minSumEndingAt}{@code .get(}{@link #endIndexOfMinSumSequence})
67
+ * is minimal across all elements in {@link #minSumEndingAt}.
68
+ */
69
+ private int endIndexOfMinSumSequence;
70
+
71
+ public KadaneExtremeSubsequenceFinderLinkedListImpl() {
72
+ sequence = new LinkedList<>();
73
+ maxSumEndingAt = new LinkedList<>();
74
+ minSumEndingAt = new LinkedList<>();
75
+ startIndexOfMaxSumSequence = new LinkedList<>();
76
+ endIndexOfMaxSumSequence = -1;
77
+ startIndexOfMinSumSequence = new LinkedList<>();
78
+ endIndexOfMinSumSequence = -1;
79
+ }
80
+
81
+ @Override
82
+ public synchronized void add(int index, T t) {
83
+ final ScalableValueWithDistance<ValueType, AveragesTo> oldMaxSum = getMaxSum();
84
+ final ScalableValueWithDistance<ValueType, AveragesTo> oldMinSum = getMinSum();
85
+ final boolean insertingIntoMaxSumSequence = index <= endIndexOfMaxSumSequence && index > startIndexOfMaxSumSequence.get(endIndexOfMaxSumSequence);
86
+ final boolean insertingIntoMinSumSequence = index <= endIndexOfMinSumSequence && index > startIndexOfMinSumSequence.get(endIndexOfMinSumSequence);
87
+ sequence.add(index, t);
88
+ final ScalableValueWithDistance<ValueType, AveragesTo> newMaxSumEndingAtIndex;
89
+ final ScalableValueWithDistance<ValueType, AveragesTo> sumWithMax = index == 0 ? null : t.add(maxSumEndingAt.get(index-1));
90
+ if (index == 0 || compare(t, sumWithMax) >= 0) {
91
+ newMaxSumEndingAtIndex = t; // one-element sum consisting of element at "index" is the maximum
92
+ startIndexOfMaxSumSequence.add(index, index);
93
+ } else {
94
+ newMaxSumEndingAtIndex = sumWithMax;
95
+ startIndexOfMaxSumSequence.add(index, startIndexOfMaxSumSequence.get(index-1));
96
+ }
97
+ maxSumEndingAt.add(index, newMaxSumEndingAtIndex);
98
+ if (oldMaxSum == null || compare(newMaxSumEndingAtIndex, oldMaxSum) > 0) {
99
+ endIndexOfMaxSumSequence = index;
100
+ }
101
+ final ScalableValueWithDistance<ValueType, AveragesTo> newMinSumEndingAtIndex;
102
+ final ScalableValueWithDistance<ValueType, AveragesTo> sumWithMin = index == 0 ? null : t.add(minSumEndingAt.get(index-1));
103
+ if (index == 0 || compare(t, sumWithMin) <= 0) {
104
+ newMinSumEndingAtIndex = t; // one-element sum consisting of element at "index" is the minimum
105
+ startIndexOfMinSumSequence.add(index, index);
106
+ } else {
107
+ newMinSumEndingAtIndex = sumWithMin;
108
+ startIndexOfMinSumSequence.add(index, startIndexOfMinSumSequence.get(index-1));
109
+ }
110
+ minSumEndingAt.add(index, newMinSumEndingAtIndex);
111
+ if (oldMinSum == null || compare(newMinSumEndingAtIndex, oldMinSum) < 0) {
112
+ endIndexOfMinSumSequence = index;
113
+ }
114
+ if (index < sequence.size()) {
115
+ update(index+1, newMaxSumEndingAtIndex, newMinSumEndingAtIndex);
116
+ }
117
+ if (insertingIntoMaxSumSequence) { // TODO probably also check whether a "positive" value was inserted; in this case, max can only grow further, and sub-sequence indices will stay unchanged
118
+ updateMax();
119
+ }
120
+ if (insertingIntoMinSumSequence) { // TODO probably also check whether a "negative" value was inserted; in this case, min can only shrink further, and sub-sequence indices will stay unchanged
121
+ updateMin();
122
+ }
123
+ }
124
+
125
+ private void updateMin() {
126
+ // TODO Auto-generated method stub
127
+
128
+ }
129
+
130
+ private void updateMax() {
131
+ // TODO Auto-generated method stub
132
+
133
+ }
134
+
135
+ private int compare(final ScalableValueWithDistance<ValueType, AveragesTo> a, final ScalableValueWithDistance<ValueType, AveragesTo> b) {
136
+ return a.divide(1).compareTo(b.divide(1));
137
+ }
138
+
139
+ /**
140
+ * For each element in {@link #sequence} starting at index {@code i}, this method checks whether the {@link #maxSumEndingAt}{@code [i]}
141
+ * still is the maximum of {@link #maxSumEndingAt}{@code [i-1]+sequence[i]} and {@link #sequence}{@code [i]}. If yes, any change to
142
+ * elements with index less than {@code i} do not have to be carried forward any further. Otherwise, {@link #maxSumEndingAt}{@code [i]}
143
+ * is updated, and the process continues at {@code i+1} "recursively" (implemented iteratively, without recursion).
144
+ */
145
+ private void update(int i, ScalableValueWithDistance<ValueType, AveragesTo> maxSumEndingAtPreviousIndex, ScalableValueWithDistance<ValueType, AveragesTo> newMinSumEndingAtIndex) {
146
+ final ListIterator<T> sequenceIter = sequence.listIterator(i);
147
+ final ListIterator<ScalableValueWithDistance<ValueType, AveragesTo>> maxSumEndingAtIter = maxSumEndingAt.listIterator(i);
148
+ final ListIterator<ScalableValueWithDistance<ValueType, AveragesTo>> minSumEndingAtIter = minSumEndingAt.listIterator(i);
149
+ final ListIterator<Integer> startIndexOfMaxSumSequenceIter = startIndexOfMaxSumSequence.listIterator(i);
150
+ final ListIterator<Integer> startIndexOfMinSumSequenceIter = startIndexOfMinSumSequence.listIterator(i);
151
+ boolean finishedMax = false;
152
+ boolean finishedMin = false;
153
+ while (sequenceIter.hasNext() && (!finishedMax || !finishedMin)) {
154
+ final T next = sequenceIter.next();
155
+ if (!finishedMax) {
156
+ final ScalableValueWithDistance<ValueType, AveragesTo> nextMaxSumEndingAt = maxSumEndingAtIter.next();
157
+ startIndexOfMaxSumSequenceIter.next();
158
+ final ScalableValueWithDistance<ValueType, AveragesTo> sum = next.add(maxSumEndingAtPreviousIndex);
159
+ final boolean nextGreaterOrEqualsThanSum = compare(next, sum) >= 0;
160
+ final ScalableValueWithDistance<ValueType, AveragesTo> newMaxSumEndingAt = nextGreaterOrEqualsThanSum ? next : sum;
161
+ if (compare(nextMaxSumEndingAt, newMaxSumEndingAt) != 0) {
162
+ maxSumEndingAtIter.remove();
163
+ maxSumEndingAtIter.add(newMaxSumEndingAt);
164
+ maxSumEndingAtPreviousIndex = newMaxSumEndingAt;
165
+ startIndexOfMaxSumSequenceIter.remove();
166
+ startIndexOfMaxSumSequenceIter.add(nextGreaterOrEqualsThanSum ? i : startIndexOfMaxSumSequence.get(i-1));
167
+ if (compare(newMaxSumEndingAt, getMaxSum()) > 0) { // FIXME getMaxSum() cannot be asked while we are still updating; indices may have moved left (remove) or right (add)!
168
+ endIndexOfMaxSumSequence = i;
169
+ }
170
+ } else {
171
+ finishedMax = true; // no more changes to propagate
172
+ }
173
+ }
174
+ if (!finishedMin) {
175
+ final ScalableValueWithDistance<ValueType, AveragesTo> nextMinSumEndingAt = minSumEndingAtIter.next();
176
+ startIndexOfMinSumSequenceIter.next();
177
+ final ScalableValueWithDistance<ValueType, AveragesTo> sum = next.add(maxSumEndingAtPreviousIndex);
178
+ final boolean nextLessOrEqualsThanSum = compare(next, sum) <= 0;
179
+ final ScalableValueWithDistance<ValueType, AveragesTo> newMinSumEndingAt = nextLessOrEqualsThanSum ? next : sum;
180
+ if (compare(nextMinSumEndingAt, newMinSumEndingAt) != 0) {
181
+ minSumEndingAtIter.remove();
182
+ minSumEndingAtIter.add(newMinSumEndingAt);
183
+ maxSumEndingAtPreviousIndex = newMinSumEndingAt;
184
+ startIndexOfMinSumSequenceIter.remove();
185
+ startIndexOfMinSumSequenceIter.add(nextLessOrEqualsThanSum ? i : startIndexOfMinSumSequence.get(i-1));
186
+ if (compare(newMinSumEndingAt, getMinSum()) < 0) {
187
+ endIndexOfMinSumSequence = i;
188
+ }
189
+ } else {
190
+ finishedMin = true; // no more changes to propagate
191
+ }
192
+ }
193
+ i++;
194
+ }
195
+ }
196
+
197
+ @Override
198
+ public synchronized void remove(int index) {
199
+ sequence.remove(index);
200
+ final ScalableValueWithDistance<ValueType, AveragesTo> maxSumEndingAtIndex = maxSumEndingAt.remove(index);
201
+ startIndexOfMaxSumSequence.remove(index);
202
+ if (endIndexOfMaxSumSequence > index) {
203
+ endIndexOfMaxSumSequence--;
204
+ } else if (endIndexOfMaxSumSequence == index) {
205
+ // TODO but if endIndexOfMaxSumSequence == index, we have deleted the last element of the max sum sequence and need to re-evaluate
206
+ }
207
+ if (endIndexOfMinSumSequence > index) {
208
+ endIndexOfMinSumSequence--;
209
+ } else if (endIndexOfMinSumSequence == index) {
210
+ // TODO but if endIndexOfMinSumSequence == index, we have deleted the last element of the min sum sequence and need to re-evaluate
211
+ }
212
+ final ScalableValueWithDistance<ValueType, AveragesTo> minSumEndingAtIndex = minSumEndingAt.remove(index);
213
+ startIndexOfMinSumSequence.remove(index);
214
+ update(index+1, maxSumEndingAtIndex, minSumEndingAtIndex);
215
+ }
216
+
217
+ @Override
218
+ public synchronized void add(T t) {
219
+ add(sequence.size(), t);
220
+ }
221
+
222
+ @Override
223
+ public synchronized void remove(T t) {
224
+ remove(sequence.indexOf(t));
225
+ }
226
+
227
+ @Override
228
+ public ScalableValueWithDistance<ValueType, AveragesTo> getMaxSum() {
229
+ return endIndexOfMaxSumSequence == -1 ? null : maxSumEndingAt.get(endIndexOfMaxSumSequence);
230
+ }
231
+
232
+ @Override
233
+ public ScalableValueWithDistance<ValueType, AveragesTo> getMinSum() {
234
+ return endIndexOfMinSumSequence == -1 ? null : minSumEndingAt.get(endIndexOfMinSumSequence);
235
+ }
236
+
237
+ @Override
238
+ public int getStartIndexOfMaxSumSequence() {
239
+ return startIndexOfMaxSumSequence.isEmpty() ? -1 : startIndexOfMaxSumSequence.get(endIndexOfMaxSumSequence);
240
+ }
241
+
242
+ /**
243
+ * @return the index into {@link #sequence} holding the last element of the contiguous sub-sequence that has the
244
+ * maximal sum; note that pointing <em>to</em> and not <em>after</em> the last element of that sequence is
245
+ * slightly different from how indices may be handled in some other from/to collection operations.
246
+ */
247
+ @Override
248
+ public int getEndIndexOfMaxSumSequence() {
249
+ return endIndexOfMaxSumSequence;
250
+ }
251
+
252
+ @Override
253
+ public int getStartIndexOfMinSumSequence() {
254
+ return startIndexOfMinSumSequence.isEmpty() ? -1 : startIndexOfMinSumSequence.get(endIndexOfMinSumSequence);
255
+ }
256
+
257
+ /**
258
+ * @return the index into {@link #sequence} holding the last element of the contiguous sub-sequence that has the
259
+ * minimal sum; note that pointing <em>to</em> and not <em>after</em> the last element of that sequence is
260
+ * slightly different from how indices may be handled in some other from/to collection operations.
261
+ */
262
+ @Override
263
+ public int getEndIndexOfMinSumSequence() {
264
+ return endIndexOfMinSumSequence;
265
+ }
266
+
267
+ @Override
268
+ public Iterator<T> iterator() {
269
+ return sequence.iterator();
270
+ }
271
+
272
+ @Override
273
+ public Iterator<T> getSubSequenceWithMaxSum() {
274
+ return sequence.subList(getStartIndexOfMaxSumSequence(), getEndIndexOfMaxSumSequence()+1).iterator();
275
+ }
276
+
277
+ @Override
278
+ public Iterator<T> getSubSequenceWithMinSum() {
279
+ return sequence.subList(getStartIndexOfMinSumSequence(), getEndIndexOfMinSumSequence()+1).iterator();
280
+ }
281
+
282
+ @Override
283
+ public int size() {
284
+ return sequence.size();
285
+ }
286
+}