java/com.sap.sse.common.test/src/com/sap/sse/common/test/KadaneExtremeSubsequenceFinderLinkedListTest.java
... ...
@@ -0,0 +1,12 @@
1
+package com.sap.sse.common.test;
2
+
3
+import org.junit.jupiter.api.BeforeEach;
4
+
5
+import com.sap.sse.common.scalablevalue.KadaneExtremeSubsequenceFinderLinkedListImpl;
6
+
7
+public class KadaneExtremeSubsequenceFinderLinkedListTest extends KadaneExtremeSubsequenceFinderTest {
8
+ @BeforeEach
9
+ public void setUp() {
10
+ finder = new KadaneExtremeSubsequenceFinderLinkedListImpl<>();
11
+ }
12
+}
java/com.sap.sse.common.test/src/com/sap/sse/common/test/KadaneExtremeSubsequenceFinderLinkedNodesTest.java
... ...
@@ -0,0 +1,12 @@
1
+package com.sap.sse.common.test;
2
+
3
+import org.junit.jupiter.api.BeforeEach;
4
+
5
+import com.sap.sse.common.scalablevalue.KadaneExtremeSubsequenceFinderLinkedNodesImpl;
6
+
7
+public class KadaneExtremeSubsequenceFinderLinkedNodesTest extends KadaneExtremeSubsequenceFinderTest {
8
+ @BeforeEach
9
+ public void setUp() {
10
+ finder = new KadaneExtremeSubsequenceFinderLinkedNodesImpl<>();
11
+ }
12
+}
java/com.sap.sse.common.test/src/com/sap/sse/common/test/KadaneExtremeSubsequenceFinderTest.java
... ...
@@ -1,22 +1,16 @@
1 1
package com.sap.sse.common.test;
2 2
3 3
import static org.junit.jupiter.api.Assertions.assertEquals;
4
+import static org.junit.jupiter.api.Assertions.assertTrue;
4 5
5
-import org.junit.jupiter.api.BeforeEach;
6 6
import org.junit.jupiter.api.Test;
7 7
8 8
import com.sap.sse.common.scalablevalue.KadaneExtremeSubsequenceFinder;
9
-import com.sap.sse.common.scalablevalue.KadaneExtremeSubsequenceFinderLinkedListImpl;
10 9
import com.sap.sse.common.scalablevalue.ScalableDouble;
11 10
12
-public class KadaneExtremeSubsequenceFinderTest {
11
+public abstract class KadaneExtremeSubsequenceFinderTest {
13 12
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
- }
13
+ protected KadaneExtremeSubsequenceFinder<Double, Double, ScalableDouble> finder;
20 14
21 15
@Test
22 16
public void testSimplePositiveSequence() {
... ...
@@ -63,10 +57,79 @@ public class KadaneExtremeSubsequenceFinderTest {
63 57
assertEquals(0, finder.getStartIndexOfMaxSumSequence());
64 58
assertEquals(4, finder.getEndIndexOfMaxSumSequence());
65 59
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...
60
+ assertEquals(9.0, finder.getMaxSum().divide(1.0), EPSILON);
67 61
assertEquals(4, finder.getStartIndexOfMaxSumSequence());
68 62
assertEquals(5, finder.getEndIndexOfMaxSumSequence());
69 63
}
70 64
71
- // TODO add tests using the remove(...) method
65
+ @Test
66
+ public void testRemoveFromMiddleOfPositiveSequence() {
67
+ finder.add(new ScalableDouble(1));
68
+ finder.add(new ScalableDouble(2));
69
+ finder.add(new ScalableDouble(3));
70
+ finder.add(new ScalableDouble(4));
71
+ finder.add(new ScalableDouble(5));
72
+ assertEquals(15.0, finder.getMaxSum().divide(1.0), EPSILON);
73
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
74
+ assertEquals(4, finder.getEndIndexOfMaxSumSequence());
75
+ finder.remove(2);
76
+ assertEquals(12.0, finder.getMaxSum().divide(1.0), EPSILON);
77
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
78
+ assertEquals(3, finder.getEndIndexOfMaxSumSequence());
79
+ }
80
+
81
+ /**
82
+ * A white-box test for how we remove from the {@link TreeSet} that holds the best max sum sub-sequence end node.
83
+ * Should the wrong node be removed, the structure would become inconsistent and hold a node as "best" that no
84
+ * longer exists.
85
+ */
86
+ @Test
87
+ public void testTwoEquallyGoodPositiveSequencesThenRemovingFromOne() {
88
+ finder.add(new ScalableDouble(1));
89
+ finder.add(new ScalableDouble(2));
90
+ finder.add(new ScalableDouble(3));
91
+ finder.add(new ScalableDouble(4));
92
+ finder.add(new ScalableDouble(5));
93
+ // now a "very negative" one that starts a new positive sequence after it
94
+ finder.add(new ScalableDouble(-100));
95
+ // now the second positive sequence with equal max sum as the first one
96
+ finder.add(new ScalableDouble(1));
97
+ finder.add(new ScalableDouble(2));
98
+ finder.add(new ScalableDouble(3));
99
+ finder.add(new ScalableDouble(4));
100
+ finder.add(new ScalableDouble(5));
101
+ assertEquals(15.0, finder.getMaxSum().divide(1.0), EPSILON);
102
+ assertTrue(finder.getStartIndexOfMaxSumSequence() == 0 || finder.getStartIndexOfMaxSumSequence() == 6);
103
+ assertTrue(finder.getEndIndexOfMaxSumSequence() == 4 || finder.getEndIndexOfMaxSumSequence() == 10);
104
+ finder.remove(3);
105
+ assertEquals(15.0, finder.getMaxSum().divide(1.0), EPSILON);
106
+ assertEquals(5, finder.getStartIndexOfMaxSumSequence());
107
+ assertEquals(9, finder.getEndIndexOfMaxSumSequence());
108
+ finder.remove(8); // removes the 4.0 from the second sequence, resulting again in two equal max sum sub-sequences:
109
+ assertEquals(11.0, finder.getMaxSum().divide(1.0), EPSILON);
110
+ assertTrue(finder.getStartIndexOfMaxSumSequence() == 0 || finder.getStartIndexOfMaxSumSequence() == 5);
111
+ assertTrue(finder.getEndIndexOfMaxSumSequence() == 3 || finder.getEndIndexOfMaxSumSequence() == 8);
112
+ finder.remove(7); // removes the 3.0 from the second sequence
113
+ assertEquals(11.0, finder.getMaxSum().divide(1.0), EPSILON);
114
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
115
+ assertEquals(3, finder.getEndIndexOfMaxSumSequence());
116
+ }
117
+
118
+ @Test
119
+ public void testRemoveFromBeginningOfPositiveSequence() {
120
+ finder.add(new ScalableDouble(1));
121
+ finder.add(new ScalableDouble(2));
122
+ finder.add(new ScalableDouble(3));
123
+ finder.add(new ScalableDouble(4));
124
+ finder.add(new ScalableDouble(5));
125
+ assertEquals(15.0, finder.getMaxSum().divide(1.0), EPSILON);
126
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
127
+ assertEquals(4, finder.getEndIndexOfMaxSumSequence());
128
+ finder.remove(0);
129
+ finder.remove(0);
130
+ finder.remove(0);
131
+ assertEquals(9.0, finder.getMaxSum().divide(1.0), EPSILON);
132
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
133
+ assertEquals(1, finder.getEndIndexOfMaxSumSequence());
134
+ }
72 135
}
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/KadaneExtremeSubsequenceFinderImpl.java
... ...
@@ -1,349 +0,0 @@
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/KadaneExtremeSubsequenceFinderLinkedNodesImpl.java
... ...
@@ -0,0 +1,460 @@
1
+package com.sap.sse.common.scalablevalue;
2
+
3
+import java.util.Comparator;
4
+import java.util.Iterator;
5
+import java.util.TreeSet;
6
+import java.util.function.BiFunction;
7
+import java.util.function.Consumer;
8
+import java.util.function.Function;
9
+
10
+import com.sap.sse.common.Util;
11
+
12
+public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>>
13
+ implements KadaneExtremeSubsequenceFinder<ValueType, AveragesTo, T> {
14
+
15
+ private static final long serialVersionUID = -8986609116472739636L;
16
+
17
+ private Node<ValueType, AveragesTo, T> first;
18
+
19
+ private Node<ValueType, AveragesTo, T> last;
20
+
21
+ private int size;
22
+
23
+ private final TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMinSum;
24
+
25
+ private final TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMaxSum;
26
+
27
+ private static <ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>> Integer compare(
28
+ final ScalableValueWithDistance<ValueType, AveragesTo> a,
29
+ final ScalableValueWithDistance<ValueType, AveragesTo> b) {
30
+ return a.divide(1).compareTo(b.divide(1));
31
+ }
32
+
33
+ /**
34
+ * Nodes of this type are used to construct a doubly-linked list, with each node holding a reference to the node
35
+ * forming the start of the sequence with the maximum sum.
36
+ *
37
+ * @author Axel Uhl (d043530)
38
+ */
39
+ private static class Node<ValueType, AveragesTo extends Comparable<AveragesTo>, T extends ComparableScalableValueWithDistance<ValueType, AveragesTo>> {
40
+ private static int idCounter = 0;
41
+ private final T value;
42
+ private final int id;
43
+ private Node<ValueType, AveragesTo, T> previous;
44
+ private Node<ValueType, AveragesTo, T> next;
45
+ private ScalableValueWithDistance<ValueType, AveragesTo> minSumEndingHere;
46
+ private Node<ValueType, AveragesTo, T> startOfMinSumSubSequenceEndingHere;
47
+ private ScalableValueWithDistance<ValueType, AveragesTo> maxSumEndingHere;
48
+ private Node<ValueType, AveragesTo, T> startOfMaxSumSubSequenceEndingHere;
49
+
50
+ private Node(Node<ValueType, AveragesTo, T> previous, Node<ValueType, AveragesTo, T> next, T value) {
51
+ super();
52
+ id = idCounter++;
53
+ this.previous = previous;
54
+ this.next = next;
55
+ this.value = value;
56
+ this.minSumEndingHere = null;
57
+ this.startOfMinSumSubSequenceEndingHere = null;
58
+ this.maxSumEndingHere = null;
59
+ this.startOfMaxSumSubSequenceEndingHere = null;
60
+ }
61
+
62
+ /**
63
+ * A unique ID that can be used, e.g., for disambiguation during sorting, so as to keep two nodes with
64
+ * {@link #getValue} values comparing equal apart.
65
+ */
66
+ private int getId() {
67
+ return id;
68
+ }
69
+
70
+ private Node<ValueType, AveragesTo, T> getPrevious() {
71
+ return previous;
72
+ }
73
+
74
+ private Node<ValueType, AveragesTo, T> getNext() {
75
+ return next;
76
+ }
77
+
78
+ private void setPrevious(Node<ValueType, AveragesTo, T> previous) {
79
+ this.previous = previous;
80
+ }
81
+
82
+ private void setNext(Node<ValueType, AveragesTo, T> next) {
83
+ this.next = next;
84
+ }
85
+
86
+ private T getValue() {
87
+ return value;
88
+ }
89
+
90
+ private ScalableValueWithDistance<ValueType, AveragesTo> getMinSumEndingHere() {
91
+ return minSumEndingHere;
92
+ }
93
+
94
+ private Node<ValueType, AveragesTo, T> getStartOfMinSumSubSequenceEndingHere() {
95
+ return startOfMinSumSubSequenceEndingHere;
96
+ }
97
+
98
+ private void setMinSumEndingHere(ScalableValueWithDistance<ValueType, AveragesTo> minSumEndingHere) {
99
+ this.minSumEndingHere = minSumEndingHere;
100
+ }
101
+
102
+ private void setStartOfMinSumSubSequenceEndingHere(Node<ValueType, AveragesTo, T> startOfMinSumSubSequenceEndingHere) {
103
+ this.startOfMinSumSubSequenceEndingHere = startOfMinSumSubSequenceEndingHere;
104
+ }
105
+
106
+ private void setMaxSumEndingHere(ScalableValueWithDistance<ValueType, AveragesTo> maxSumEndingHere) {
107
+ this.maxSumEndingHere = maxSumEndingHere;
108
+ }
109
+
110
+ private void setStartOfMaxSumSubSequenceEndingHere(Node<ValueType, AveragesTo, T> startOfMaxSumSubSequenceEndingHere) {
111
+ this.startOfMaxSumSubSequenceEndingHere = startOfMaxSumSubSequenceEndingHere;
112
+ }
113
+
114
+ private ScalableValueWithDistance<ValueType, AveragesTo> getMaxSumEndingHere() {
115
+ return maxSumEndingHere;
116
+ }
117
+
118
+ private Node<ValueType, AveragesTo, T> getStartOfMaxSumSubSequenceEndingHere() {
119
+ return startOfMaxSumSubSequenceEndingHere;
120
+ }
121
+
122
+ /**
123
+ * Updates this node's extreme sum values of the sub-sequences ending at this node, considering the values
124
+ * stored in the {@link #getPrevious() previous} element, if such an element exists. Furthermore, the
125
+ * references to the nodes where these sub-sequences start are updated accordingly.
126
+ *
127
+ * @return whether the node has changed during this update; this may mean a change in the min/max sum and/or the
128
+ * the start of the extreme sub-sequence(s) ending at this node. Any such change requires updating
129
+ * {@link #getNext() following nodes} too.
130
+ */
131
+ private boolean updateThisFromPrevious() {
132
+ final boolean changedByMax = updateMaxFromPrevious();
133
+ final boolean changedByMin = updateMinFromPrevious();
134
+ return changedByMax || changedByMin;
135
+ }
136
+
137
+ /**
138
+ * Updates this node's min sum values of the sub-sequences ending at this node, considering the values stored in
139
+ * the {@link #getPrevious() previous} element, if such an element exists. Furthermore, the references to the
140
+ * node where the min sum sub-sequence starts is updated accordingly.
141
+ *
142
+ * @return whether the node's min sum-related properties changed during this update; this may mean a change in
143
+ * the min sum and/or the the start of the min sum sub-sequence(s) ending at this node. Any such change
144
+ * requires updating {@link #getNext() following nodes} using this method, too. It does not happen
145
+ * automatically by calling this method. This method updates only this node.
146
+ */
147
+ private boolean updateMinFromPrevious() {
148
+ return updateThisFromPrevious(Node::getMinSumEndingHere,
149
+ Node::getStartOfMinSumSubSequenceEndingHere, this::setMinSumEndingHere,
150
+ this::setStartOfMinSumSubSequenceEndingHere, (a, b)->compare(b, a));
151
+ }
152
+
153
+ /**
154
+ * Updates this node's max sum values of the sub-sequences ending at this node, considering the values stored in
155
+ * the {@link #getPrevious() previous} element, if such an element exists. Furthermore, the references to the
156
+ * node where the max sum sub-sequence starts is updated accordingly.
157
+ *
158
+ * @return whether the node's max sum-related properties changed during this update; this may mean a change in
159
+ * the max sum and/or the the start of the max sum sub-sequence(s) ending at this node. Any such change
160
+ * requires updating {@link #getNext() following nodes} using this method, too. It does not happen
161
+ * automatically by calling this method. This method updates only this node.
162
+ */
163
+ private boolean updateMaxFromPrevious() {
164
+ return updateThisFromPrevious(Node::getMaxSumEndingHere,
165
+ Node::getStartOfMaxSumSubSequenceEndingHere, this::setMaxSumEndingHere,
166
+ this::setStartOfMaxSumSubSequenceEndingHere, KadaneExtremeSubsequenceFinderLinkedNodesImpl::compare);
167
+ }
168
+
169
+ private boolean updateThisFromPrevious(Function<Node<ValueType, AveragesTo, T>, ScalableValueWithDistance<ValueType, AveragesTo>> getExtremeSumEndingHere,
170
+ Function<Node<ValueType, AveragesTo, T>, Node<ValueType, AveragesTo, T>> getStartOfExtremeSumSubSequenceEndingHere,
171
+ Consumer<ScalableValueWithDistance<ValueType, AveragesTo>> setExtremeSumEndingHere,
172
+ Consumer<Node<ValueType, AveragesTo, T>> setStartOfExtremeSubSubSequenceEndingHere,
173
+ BiFunction<ScalableValueWithDistance<ValueType, AveragesTo>, ScalableValueWithDistance<ValueType, AveragesTo>, Integer> comparator) {
174
+ boolean changed = false;
175
+ final ScalableValueWithDistance<ValueType, AveragesTo> newMaxSumEndingAtIndex;
176
+ final Node<ValueType, AveragesTo, T> newStartOfMaxSumSubSequenceEndingHere;
177
+ final ScalableValueWithDistance<ValueType, AveragesTo> sumWithMax = getPrevious() == null ? null : getValue().add(getExtremeSumEndingHere.apply(getPrevious()));
178
+ if (getPrevious() == null || comparator.apply(getValue(), sumWithMax) >= 0) {
179
+ newMaxSumEndingAtIndex = getValue(); // one-element sum consisting of element at "index" is the maximum
180
+ newStartOfMaxSumSubSequenceEndingHere = this;
181
+ } else {
182
+ newMaxSumEndingAtIndex = sumWithMax;
183
+ newStartOfMaxSumSubSequenceEndingHere = getStartOfExtremeSumSubSequenceEndingHere.apply(getPrevious());
184
+ }
185
+ if (!newMaxSumEndingAtIndex.equals(getExtremeSumEndingHere.apply(this))) {
186
+ changed = true;
187
+ setExtremeSumEndingHere.accept(newMaxSumEndingAtIndex);
188
+ }
189
+ if (newStartOfMaxSumSubSequenceEndingHere != getStartOfExtremeSumSubSequenceEndingHere.apply(this)) {
190
+ changed = true;
191
+ setStartOfExtremeSubSubSequenceEndingHere.accept(newStartOfMaxSumSubSequenceEndingHere);
192
+ }
193
+ return changed;
194
+ }
195
+
196
+ @Override
197
+ public String toString() {
198
+ return "["+getValue()+", "+"max: "+getMaxSumEndingHere()+", min: "+getMinSumEndingHere()+"]";
199
+ }
200
+ }
201
+
202
+ public KadaneExtremeSubsequenceFinderLinkedNodesImpl() {
203
+ this.size = 0;
204
+ this.first = null;
205
+ this.last = null;
206
+ final Comparator<Node<ValueType, AveragesTo, T>> minSumComparator = (n1, n2)->compare(n1.getMinSumEndingHere(), n2.getMinSumEndingHere());
207
+ final Comparator<Node<ValueType, AveragesTo, T>> maxSumComparator = (n1, n2)->compare(n1.getMaxSumEndingHere(), n2.getMaxSumEndingHere());
208
+ this.nodesOrderedByMinSum = new TreeSet<>(minSumComparator.thenComparing((n1, n2)->Integer.compare(n1.getId(), n2.getId())));
209
+ this.nodesOrderedByMaxSum = new TreeSet<>(maxSumComparator.thenComparing((n1, n2)->Integer.compare(n1.getId(), n2.getId())));
210
+ }
211
+
212
+ @Override
213
+ public int size() {
214
+ return size;
215
+ }
216
+
217
+ @Override
218
+ public boolean isEmpty() {
219
+ return first == null;
220
+ }
221
+
222
+ private Iterable<Node<ValueType, AveragesTo, T>> getNodeIterable() {
223
+ return this::nodeIterator;
224
+ }
225
+
226
+ private Iterator<Node<ValueType, AveragesTo, T>> nodeIterator() {
227
+ return nodeIterator(first, last);
228
+ }
229
+
230
+ private Iterator<Node<ValueType, AveragesTo, T>> nodeIterator(final Node<ValueType, AveragesTo, T> firstNode, final Node<ValueType, AveragesTo, T> lastNode) {
231
+ return new Iterator<Node<ValueType, AveragesTo, T>>() {
232
+ private Node<ValueType, AveragesTo, T> current = firstNode;
233
+
234
+ @Override
235
+ public boolean hasNext() {
236
+ return current != (lastNode==null ? null : lastNode.getNext());
237
+ }
238
+
239
+ @Override
240
+ public Node<ValueType, AveragesTo, T> next() {
241
+ final Node<ValueType, AveragesTo, T> result = current;
242
+ current = current.getNext();
243
+ return result;
244
+ }
245
+ };
246
+ }
247
+
248
+ @Override
249
+ public Iterator<T> iterator() {
250
+ return Util.map(getNodeIterable(), node->node.getValue()).iterator();
251
+ }
252
+
253
+ @Override
254
+ public void add(int index, T t) {
255
+ if (index < 0 || index > size()) {
256
+ throw new IndexOutOfBoundsException("Trying to add at index "+index+" to a sequence of size "+size());
257
+ }
258
+ final Node<ValueType, AveragesTo, T> node;
259
+ if (isEmpty()) {
260
+ node = new Node<>(/* previous */ null, /* next */ null, t);
261
+ first = node;
262
+ last = node;
263
+ node.updateThisFromPrevious(); // no need to consider changes; it's the only element
264
+ } else {
265
+ final Node<ValueType, AveragesTo, T> nodeBeforeIndex;
266
+ final Node<ValueType, AveragesTo, T> nodeAfterIndex;
267
+ if (index>0) {
268
+ nodeBeforeIndex = getNode(index-1);
269
+ nodeAfterIndex = nodeBeforeIndex.getNext();
270
+ } else if (index<size()) {
271
+ nodeAfterIndex = getNode(index);
272
+ nodeBeforeIndex = nodeAfterIndex.getPrevious();
273
+ } else {
274
+ throw new InternalError("This shouldn't have happened as it means the sequence is empty, and we shouldn't have arrived in this branch");
275
+ }
276
+ node = new Node<>(nodeBeforeIndex, nodeAfterIndex, t);
277
+ if (nodeBeforeIndex != null) {
278
+ nodeBeforeIndex.setNext(node);
279
+ } else {
280
+ first = node;
281
+ }
282
+ if (nodeAfterIndex != null) {
283
+ nodeAfterIndex.setPrevious(node);
284
+ } else {
285
+ last = node;
286
+ }
287
+ node.updateThisFromPrevious();
288
+ if (nodeBeforeIndex == null
289
+ || !node.getMaxSumEndingHere().equals(nodeBeforeIndex.getMaxSumEndingHere())
290
+ || !node.getMinSumEndingHere().equals(nodeBeforeIndex.getMinSumEndingHere())
291
+ || node.getStartOfMaxSumSubSequenceEndingHere() != nodeBeforeIndex.getStartOfMaxSumSubSequenceEndingHere()
292
+ || node.getStartOfMinSumSubSequenceEndingHere().equals(nodeBeforeIndex.getStartOfMinSumSubSequenceEndingHere())) {
293
+ // the inserted node differs from the previous node in one of the extreme sums ending at it, and/or
294
+ // regarding where those sub-sequences start, so we need to propagate the updates to subsequent nodes
295
+ propagateChanges(node);
296
+ }
297
+ }
298
+ nodesOrderedByMinSum.add(node);
299
+ nodesOrderedByMaxSum.add(node);
300
+ size++;
301
+ }
302
+
303
+ /**
304
+ * Propagates changes to the {@link Node#getNext() next} node that follows {@code node}, and all further ones up to
305
+ * the end of the collection or until no more changes occur that need propagating. The {@link #nodesOrderedByMinSum}
306
+ * and {@link #nodesOrderedByMaxSum} collections are updated accordingly.
307
+ */
308
+ private void propagateChanges(Node<ValueType, AveragesTo, T> node) {
309
+ boolean changedMin = true;
310
+ boolean changedMax = true;
311
+ propagateChanges(node, changedMin, changedMax);
312
+ }
313
+
314
+ /**
315
+ * Propagates changes to the {@link Node#getNext() next} node that follows {@code node}, and all further ones up to
316
+ * the end of the collection or until no more changes occur that need propagating. The {@link #nodesOrderedByMinSum}
317
+ * and {@link #nodesOrderedByMaxSum} collections are updated accordingly.
318
+ *
319
+ * @param changedMin
320
+ * tells if changes to {@code node}'s minimum sum sub-sequences need propagation
321
+ * @param changedMax
322
+ * tells if changes to {@code node}'s maximum sum sub-sequences need propagation
323
+ */
324
+ private void propagateChanges(Node<ValueType, AveragesTo, T> node, boolean changedMin, boolean changedMax) {
325
+ Node<ValueType, AveragesTo, T> current = node.getNext();
326
+ while ((changedMin || changedMax) && current != null) {
327
+ if (changedMin) {
328
+ nodesOrderedByMinSum.remove(current);
329
+ changedMin = current.updateMinFromPrevious();
330
+ nodesOrderedByMinSum.add(current);
331
+ }
332
+ if (changedMax) {
333
+ nodesOrderedByMaxSum.remove(current);
334
+ changedMax = current.updateMaxFromPrevious();
335
+ nodesOrderedByMaxSum.add(current);
336
+ }
337
+ current = current.getNext();
338
+ }
339
+ }
340
+
341
+ private Node<ValueType, AveragesTo, T> getNode(int index) {
342
+ if (index < 0 || index >= size()) {
343
+ throw new IndexOutOfBoundsException("Trying to find node at index "+index+" in a sequence of size "+size());
344
+ }
345
+ final Node<ValueType, AveragesTo, T> result;
346
+ if (isEmpty()) {
347
+ result = null;
348
+ } else {
349
+ if (index > size()/2) { // search from the end
350
+ result = step(last, size()-1-index, Node::getPrevious);
351
+ } else { // search from the beginning
352
+ result = step(first, index, Node::getNext);
353
+ }
354
+ }
355
+ return result;
356
+ }
357
+
358
+ private Node<ValueType, AveragesTo, T> step(Node<ValueType, AveragesTo, T> start, int numberOfSteps,
359
+ Function<Node<ValueType, AveragesTo, T>, Node<ValueType, AveragesTo, T>> stepper) {
360
+ Node<ValueType, AveragesTo, T> current = start;
361
+ for (int i=0; i<numberOfSteps; i++) {
362
+ current = stepper.apply(current);
363
+ }
364
+ return current;
365
+ }
366
+
367
+ @Override
368
+ public void remove(int index) {
369
+ final Node<ValueType, AveragesTo, T> node = getNode(index);
370
+ assert node != null; // otherwise, an IndexOutOfBoundsException should have been thrown
371
+ remove(node);
372
+ }
373
+
374
+ private void remove(Node<ValueType, AveragesTo, T> node) {
375
+ if (node.getPrevious() != null) {
376
+ node.getPrevious().setNext(node.getNext());
377
+ } else {
378
+ assert first == node;
379
+ first = node.getNext();
380
+ }
381
+ if (node.getNext() != null) {
382
+ node.getNext().setPrevious(node.getPrevious());
383
+ final boolean changedMin = node.getPrevious() == null
384
+ || !node.getMinSumEndingHere().equals(node.getPrevious().getMinSumEndingHere())
385
+ || node.getStartOfMinSumSubSequenceEndingHere() != node.getPrevious()
386
+ .getStartOfMinSumSubSequenceEndingHere();
387
+ final boolean changedMax = node.getPrevious() == null
388
+ || !node.getMaxSumEndingHere().equals(node.getPrevious().getMaxSumEndingHere())
389
+ || node.getStartOfMaxSumSubSequenceEndingHere() != node.getPrevious()
390
+ .getStartOfMaxSumSubSequenceEndingHere();
391
+ propagateChanges(node, changedMin, changedMax);
392
+ } else {
393
+ assert last == node;
394
+ last = node.getPrevious();
395
+ }
396
+ size--;
397
+ }
398
+
399
+ @Override
400
+ public void remove(T t) {
401
+ Node<ValueType, AveragesTo, T> node = first;
402
+ while (node != null && !node.getValue().equals(t)) {
403
+ node = node.getNext();
404
+ }
405
+ if (node != null) {
406
+ assert node.getValue().equals(t);
407
+ remove(node);
408
+ }
409
+ }
410
+
411
+ @Override
412
+ public ScalableValueWithDistance<ValueType, AveragesTo> getMinSum() {
413
+ final Node<ValueType, AveragesTo, T> first = nodesOrderedByMinSum.first();
414
+ return first == null ? null : first.getMinSumEndingHere();
415
+ }
416
+
417
+ @Override
418
+ public ScalableValueWithDistance<ValueType, AveragesTo> getMaxSum() {
419
+ final Node<ValueType, AveragesTo, T> last = nodesOrderedByMaxSum.last();
420
+ return last == null ? null : last.getMaxSumEndingHere();
421
+ }
422
+
423
+ @Override
424
+ public int getStartIndexOfMaxSumSequence() {
425
+ final Node<ValueType, AveragesTo, T> nodeWhereBestMaxSumSubSequenceEnds = nodesOrderedByMaxSum.last();
426
+ return nodeWhereBestMaxSumSubSequenceEnds == null ? -1 : Util.indexOf(getNodeIterable(), nodeWhereBestMaxSumSubSequenceEnds.getStartOfMaxSumSubSequenceEndingHere());
427
+ }
428
+
429
+ @Override
430
+ public int getEndIndexOfMaxSumSequence() {
431
+ final Node<ValueType, AveragesTo, T> nodeWhereBestMaxSumSubSequenceEnds = nodesOrderedByMaxSum.last();
432
+ return nodeWhereBestMaxSumSubSequenceEnds == null ? -1 : Util.indexOf(getNodeIterable(), nodeWhereBestMaxSumSubSequenceEnds);
433
+ }
434
+
435
+ @Override
436
+ public int getStartIndexOfMinSumSequence() {
437
+ final Node<ValueType, AveragesTo, T> nodeWhereBestMinSumSubSequenceEnds = nodesOrderedByMinSum.first();
438
+ return nodeWhereBestMinSumSubSequenceEnds == null ? -1 : Util.indexOf(getNodeIterable(), nodeWhereBestMinSumSubSequenceEnds.getStartOfMinSumSubSequenceEndingHere());
439
+ }
440
+
441
+ @Override
442
+ public int getEndIndexOfMinSumSequence() {
443
+ final Node<ValueType, AveragesTo, T> nodeWhereBestMinSumSubSequenceEnds = nodesOrderedByMinSum.first();
444
+ return nodeWhereBestMinSumSubSequenceEnds == null ? -1 : Util.indexOf(getNodeIterable(), nodeWhereBestMinSumSubSequenceEnds);
445
+ }
446
+
447
+ @Override
448
+ public Iterator<T> getSubSequenceWithMaxSum() {
449
+ final Node<ValueType, AveragesTo, T> nodeWhereBestMaxSumSubSequenceEnds = nodesOrderedByMaxSum.last();
450
+ final Iterable<Node<ValueType, AveragesTo, T>> nodeIterable = ()->nodeIterator(nodeWhereBestMaxSumSubSequenceEnds.getStartOfMaxSumSubSequenceEndingHere(), nodeWhereBestMaxSumSubSequenceEnds);
451
+ return Util.map(nodeIterable, node->node.getValue()).iterator();
452
+ }
453
+
454
+ @Override
455
+ public Iterator<T> getSubSequenceWithMinSum() {
456
+ final Node<ValueType, AveragesTo, T> nodeWhereBestMinSumSubSequenceEnds = nodesOrderedByMinSum.first();
457
+ final Iterable<Node<ValueType, AveragesTo, T>> nodeIterable = ()->nodeIterator(nodeWhereBestMinSumSubSequenceEnds.getStartOfMinSumSubSequenceEndingHere(), nodeWhereBestMinSumSubSequenceEnds);
458
+ return Util.map(nodeIterable, node->node.getValue()).iterator();
459
+ }
460
+}