java/com.sap.sse.common.test/src/com/sap/sse/common/test/KadaneExtremeSubsequenceFinderTest.java
... ...
@@ -207,6 +207,22 @@ public abstract class KadaneExtremeSubsequenceFinderTest {
207 207
logger.info("Stats after pruning from beginning: " + finder.toString());
208 208
}
209 209
210
+ @Test
211
+ public void testSingleNegativeNumber() {
212
+ finder.add(new ScalableDouble(1));
213
+ finder.add(new ScalableDouble(2));
214
+ finder.add(new ScalableDouble(3));
215
+ finder.add(new ScalableDouble(4));
216
+ finder.add(new ScalableDouble(-38.001708984375));
217
+ finder.removeFirst(4);
218
+ assertEquals(-38.001708984375, finder.getMaxSum().divide(1.0), EPSILON);
219
+ assertEquals(0, finder.getStartIndexOfMaxSumSequence());
220
+ assertEquals(0, finder.getEndIndexOfMaxSumSequence());
221
+ assertEquals(-38.001708984375, finder.getMinSum().divide(1.0), EPSILON);
222
+ assertEquals(0, finder.getStartIndexOfMinSumSequence());
223
+ assertEquals(0, finder.getEndIndexOfMinSumSequence());
224
+ }
225
+
210 226
@AfterAll
211 227
public static void writeMeasurements() throws IOException {
212 228
performanceReport.write();
java/com.sap.sse.common/src/com/sap/sse/common/scalablevalue/KadaneExtremeSubsequenceFinderLinkedNodesImpl.java
... ...
@@ -1,5 +1,6 @@
1 1
package com.sap.sse.common.scalablevalue;
2 2
3
+import java.util.Collections;
3 4
import java.util.Comparator;
4 5
import java.util.Iterator;
5 6
import java.util.TreeSet;
... ...
@@ -148,9 +149,10 @@ public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo
148 149
* the start of the extreme sub-sequence(s) ending at this node. Any such change requires updating
149 150
* {@link #getNext() following nodes} too.
150 151
*/
151
- private boolean updateThisFromPrevious() {
152
- final boolean changedByMax = updateMaxFromPrevious();
153
- final boolean changedByMin = updateMinFromPrevious();
152
+ private boolean updateThisFromPrevious(TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMinSum, TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMaxSum) {
153
+ final boolean changedByMin = updateMinFromPrevious(nodesOrderedByMinSum);
154
+ final boolean changedByMax = updateMaxFromPrevious(nodesOrderedByMaxSum);
155
+ assert nodesOrderedByMinSum.size() == nodesOrderedByMaxSum.size();
154 156
return changedByMax || changedByMin;
155 157
}
156 158
... ...
@@ -164,10 +166,10 @@ public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo
164 166
* requires updating {@link #getNext() following nodes} using this method, too. It does not happen
165 167
* automatically by calling this method. This method updates only this node.
166 168
*/
167
- private boolean updateMinFromPrevious() {
169
+ private boolean updateMinFromPrevious(TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMinSum) {
168 170
return updateThisFromPrevious(Node::getMinSumEndingHere,
169 171
Node::getStartOfMinSumSubSequenceEndingHere, this::setMinSumEndingHere,
170
- this::setStartOfMinSumSubSequenceEndingHere, (a, b)->compare(b, a));
172
+ this::setStartOfMinSumSubSequenceEndingHere, (a, b)->compare(b, a), nodesOrderedByMinSum);
171 173
}
172 174
173 175
/**
... ...
@@ -180,31 +182,41 @@ public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo
180 182
* requires updating {@link #getNext() following nodes} using this method, too. It does not happen
181 183
* automatically by calling this method. This method updates only this node.
182 184
*/
183
- private boolean updateMaxFromPrevious() {
185
+ private boolean updateMaxFromPrevious(TreeSet<Node<ValueType, AveragesTo, T>> nodesOrderedByMaxSum) {
184 186
return updateThisFromPrevious(Node::getMaxSumEndingHere,
185 187
Node::getStartOfMaxSumSubSequenceEndingHere, this::setMaxSumEndingHere,
186
- this::setStartOfMaxSumSubSequenceEndingHere, KadaneExtremeSubsequenceFinderLinkedNodesImpl::compare);
188
+ this::setStartOfMaxSumSubSequenceEndingHere, KadaneExtremeSubsequenceFinderLinkedNodesImpl::compare, nodesOrderedByMaxSum);
187 189
}
188 190
189 191
private boolean updateThisFromPrevious(Function<Node<ValueType, AveragesTo, T>, ScalableValueWithDistance<ValueType, AveragesTo>> getExtremeSumEndingHere,
190 192
Function<Node<ValueType, AveragesTo, T>, Node<ValueType, AveragesTo, T>> getStartOfExtremeSumSubSequenceEndingHere,
191 193
Consumer<ScalableValueWithDistance<ValueType, AveragesTo>> setExtremeSumEndingHere,
192 194
Consumer<Node<ValueType, AveragesTo, T>> setStartOfExtremeSubSubSequenceEndingHere,
193
- BiFunction<ScalableValueWithDistance<ValueType, AveragesTo>, ScalableValueWithDistance<ValueType, AveragesTo>, Integer> comparator) {
195
+ BiFunction<ScalableValueWithDistance<ValueType, AveragesTo>, ScalableValueWithDistance<ValueType, AveragesTo>, Integer> comparator,
196
+ TreeSet<Node<ValueType, AveragesTo, T>> mapOrderedByExtremeSumEndingHereToUpdate) {
194 197
boolean changed = false;
195
- final ScalableValueWithDistance<ValueType, AveragesTo> newMaxSumEndingAtIndex;
198
+ final ScalableValueWithDistance<ValueType, AveragesTo> newMaxSumEndingHere;
196 199
final Node<ValueType, AveragesTo, T> newStartOfMaxSumSubSequenceEndingHere;
197 200
final ScalableValueWithDistance<ValueType, AveragesTo> sumWithMax = getPrevious() == null ? null : getValue().add(getExtremeSumEndingHere.apply(getPrevious()));
198 201
if (getPrevious() == null || comparator.apply(getValue(), sumWithMax) >= 0) {
199
- newMaxSumEndingAtIndex = getValue(); // one-element sum consisting of element at "index" is the maximum
202
+ newMaxSumEndingHere = getValue(); // one-element sum consisting of element at "index" is the maximum
200 203
newStartOfMaxSumSubSequenceEndingHere = this;
201 204
} else {
202
- newMaxSumEndingAtIndex = sumWithMax;
205
+ newMaxSumEndingHere = sumWithMax;
203 206
newStartOfMaxSumSubSequenceEndingHere = getStartOfExtremeSumSubSequenceEndingHere.apply(getPrevious());
204 207
}
205
- if (!newMaxSumEndingAtIndex.equals(getExtremeSumEndingHere.apply(this))) {
208
+ final ScalableValueWithDistance<ValueType, AveragesTo> oldExtremeSumEndingHere = getExtremeSumEndingHere.apply(this);
209
+ if (!newMaxSumEndingHere.equals(oldExtremeSumEndingHere)) {
206 210
changed = true;
207
- setExtremeSumEndingHere.accept(newMaxSumEndingAtIndex);
211
+ if (oldExtremeSumEndingHere != null) {
212
+ if (!mapOrderedByExtremeSumEndingHereToUpdate.remove(this)) {
213
+ throw new InternalError("This shouldn't have happened as it means the node was not present in the map, although it should have been");
214
+ }
215
+ }
216
+ setExtremeSumEndingHere.accept(newMaxSumEndingHere);
217
+ if (newMaxSumEndingHere != null) {
218
+ mapOrderedByExtremeSumEndingHereToUpdate.add(this);
219
+ }
208 220
}
209 221
if (newStartOfMaxSumSubSequenceEndingHere != getStartOfExtremeSumSubSequenceEndingHere.apply(this)) {
210 222
changed = true;
... ...
@@ -223,11 +235,13 @@ public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo
223 235
this.size = 0;
224 236
this.first = null;
225 237
this.last = null;
238
+ final Comparator<? super Node<ValueType, AveragesTo, T>> idComparator = (n1, n2)->Integer.compare(n1.getId(), n2.getId());
226 239
final Comparator<Node<ValueType, AveragesTo, T>> minSumComparator = (n1, n2)->compare(n1.getMinSumEndingHere(), n2.getMinSumEndingHere());
227 240
final Comparator<Node<ValueType, AveragesTo, T>> maxSumComparator = (n1, n2)->compare(n1.getMaxSumEndingHere(), n2.getMaxSumEndingHere());
228
- final Comparator<? super Node<ValueType, AveragesTo, T>> idComparator = (n1, n2)->Integer.compare(n1.getId(), n2.getId());
229
- this.nodesOrderedByMinSum = new TreeSet<>((n1,n2)->(n1==n2?0:minSumComparator.thenComparing(idComparator).compare(n1, n2)));
230
- this.nodesOrderedByMaxSum = new TreeSet<>((n1,n2)->(n1==n2?0:maxSumComparator.thenComparing(idComparator).compare(n1, n2)));
241
+ final Comparator<? super Node<ValueType, AveragesTo, T>> minSumOuterComparator = (n1,n2)->(n1==n2?0:minSumComparator.thenComparing(idComparator).compare(n1, n2));
242
+ final Comparator<? super Node<ValueType, AveragesTo, T>> maxSumOuterComparator = (n1,n2)->(n1==n2?0:maxSumComparator.thenComparing(idComparator).compare(n1, n2));
243
+ this.nodesOrderedByMinSum = new TreeSet<>(minSumOuterComparator);
244
+ this.nodesOrderedByMaxSum = new TreeSet<>(maxSumOuterComparator);
231 245
}
232 246
233 247
@Override
... ...
@@ -281,7 +295,7 @@ public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo
281 295
node = new Node<>(/* previous */ null, /* next */ null, t);
282 296
first = node;
283 297
last = node;
284
- node.updateThisFromPrevious(); // no need to consider changes; it's the only element
298
+ node.updateThisFromPrevious(nodesOrderedByMinSum, nodesOrderedByMaxSum); // no need to consider changes; it's the only element
285 299
} else {
286 300
final Node<ValueType, AveragesTo, T> nodeBeforeIndex;
287 301
final Node<ValueType, AveragesTo, T> nodeAfterIndex;
... ...
@@ -305,7 +319,7 @@ public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo
305 319
} else {
306 320
last = node;
307 321
}
308
- node.updateThisFromPrevious();
322
+ node.updateThisFromPrevious(nodesOrderedByMinSum, nodesOrderedByMaxSum); // manages addition to tree maps
309 323
if (nodeBeforeIndex == null
310 324
|| !node.getMaxSumEndingHere().equals(nodeBeforeIndex.getMaxSumEndingHere())
311 325
|| !node.getMinSumEndingHere().equals(nodeBeforeIndex.getMinSumEndingHere())
... ...
@@ -316,8 +330,6 @@ public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo
316 330
propagateChanges(node);
317 331
}
318 332
}
319
- nodesOrderedByMinSum.add(node);
320
- nodesOrderedByMaxSum.add(node);
321 333
size++;
322 334
}
323 335
... ...
@@ -349,17 +361,14 @@ public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo
349 361
while ((changedMin || changedMax) && current != null) {
350 362
if (changedMin) {
351 363
minChangeCount++;
352
- nodesOrderedByMinSum.remove(current);
353
- changedMin = current.updateMinFromPrevious();
354
- nodesOrderedByMinSum.add(current);
364
+ changedMin = current.updateMinFromPrevious(nodesOrderedByMinSum);
355 365
}
356 366
if (changedMax) {
357 367
maxChangeCount++;
358
- nodesOrderedByMaxSum.remove(current);
359
- changedMax = current.updateMaxFromPrevious();
360
- nodesOrderedByMaxSum.add(current);
368
+ changedMax = current.updateMaxFromPrevious(nodesOrderedByMaxSum);
361 369
}
362 370
current = current.getNext();
371
+ assert nodesOrderedByMinSum.size() == nodesOrderedByMaxSum.size();
363 372
}
364 373
minChangePropagationStepsSum += minChangeCount;
365 374
minChangePropagationsCount++;
... ...
@@ -424,6 +433,7 @@ public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo
424 433
}
425 434
nodesOrderedByMinSum.remove(node);
426 435
nodesOrderedByMaxSum.remove(node);
436
+ assert nodesOrderedByMinSum.size() == nodesOrderedByMaxSum.size();
427 437
size--;
428 438
}
429 439
... ...
@@ -454,11 +464,12 @@ public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo
454 464
while (nodeRemoved != null) {
455 465
nodesOrderedByMinSum.remove(nodeRemoved);
456 466
nodesOrderedByMaxSum.remove(nodeRemoved);
467
+ assert nodesOrderedByMinSum.size() == nodesOrderedByMaxSum.size();
457 468
nodeRemoved = nodeRemoved.getPrevious();
458 469
}
459 470
if (first != null) {
460 471
first.setPrevious(null);
461
- if (first.updateThisFromPrevious()) {
472
+ if (first.updateThisFromPrevious(nodesOrderedByMinSum, nodesOrderedByMaxSum)) {
462 473
propagateChanges(first);
463 474
}
464 475
} else {
... ...
@@ -471,52 +482,58 @@ public class KadaneExtremeSubsequenceFinderLinkedNodesImpl<ValueType, AveragesTo
471 482
472 483
@Override
473 484
public ScalableValueWithDistance<ValueType, AveragesTo> getMinSum() {
474
- final Node<ValueType, AveragesTo, T> first = nodesOrderedByMinSum.first();
475
- return first == null ? null : first.getMinSumEndingHere();
485
+ return isEmpty() ? null : nodesOrderedByMinSum.first().getMinSumEndingHere();
476 486
}
477 487
478 488
@Override
479 489
public ScalableValueWithDistance<ValueType, AveragesTo> getMaxSum() {
480
- final Node<ValueType, AveragesTo, T> last = nodesOrderedByMaxSum.last();
481
- return last == null ? null : last.getMaxSumEndingHere();
490
+ return isEmpty() ? null : nodesOrderedByMaxSum.last().getMaxSumEndingHere();
482 491
}
483 492
484 493
@Override
485 494
public int getStartIndexOfMaxSumSequence() {
486
- final Node<ValueType, AveragesTo, T> nodeWhereBestMaxSumSubSequenceEnds = nodesOrderedByMaxSum.last();
487
- return nodeWhereBestMaxSumSubSequenceEnds == null ? -1 : Util.indexOf(getNodeIterable(), nodeWhereBestMaxSumSubSequenceEnds.getStartOfMaxSumSubSequenceEndingHere());
495
+ return isEmpty() ? -1 : Util.indexOf(getNodeIterable(), nodesOrderedByMaxSum.last().getStartOfMaxSumSubSequenceEndingHere());
488 496
}
489 497
490 498
@Override
491 499
public int getEndIndexOfMaxSumSequence() {
492
- final Node<ValueType, AveragesTo, T> nodeWhereBestMaxSumSubSequenceEnds = nodesOrderedByMaxSum.last();
493
- return nodeWhereBestMaxSumSubSequenceEnds == null ? -1 : Util.indexOf(getNodeIterable(), nodeWhereBestMaxSumSubSequenceEnds);
500
+ return isEmpty() ? -1 : Util.indexOf(getNodeIterable(), nodesOrderedByMaxSum.last());
494 501
}
495 502
496 503
@Override
497 504
public int getStartIndexOfMinSumSequence() {
498
- final Node<ValueType, AveragesTo, T> nodeWhereBestMinSumSubSequenceEnds = nodesOrderedByMinSum.first();
499
- return nodeWhereBestMinSumSubSequenceEnds == null ? -1 : Util.indexOf(getNodeIterable(), nodeWhereBestMinSumSubSequenceEnds.getStartOfMinSumSubSequenceEndingHere());
505
+ return isEmpty() ? -1 : Util.indexOf(getNodeIterable(), nodesOrderedByMinSum.first().getStartOfMinSumSubSequenceEndingHere());
500 506
}
501 507
502 508
@Override
503 509
public int getEndIndexOfMinSumSequence() {
504
- final Node<ValueType, AveragesTo, T> nodeWhereBestMinSumSubSequenceEnds = nodesOrderedByMinSum.first();
505
- return nodeWhereBestMinSumSubSequenceEnds == null ? -1 : Util.indexOf(getNodeIterable(), nodeWhereBestMinSumSubSequenceEnds);
510
+ return isEmpty() ? -1 : Util.indexOf(getNodeIterable(), nodesOrderedByMinSum.first());
506 511
}
507 512
508 513
@Override
509 514
public Iterator<T> getSubSequenceWithMaxSum() {
510
- final Node<ValueType, AveragesTo, T> nodeWhereBestMaxSumSubSequenceEnds = nodesOrderedByMaxSum.last();
511
- final Iterable<Node<ValueType, AveragesTo, T>> nodeIterable = ()->nodeIterator(nodeWhereBestMaxSumSubSequenceEnds.getStartOfMaxSumSubSequenceEndingHere(), nodeWhereBestMaxSumSubSequenceEnds);
512
- return Util.map(nodeIterable, node->node.getValue()).iterator();
515
+ final Iterator<T> result;
516
+ if (isEmpty()) {
517
+ result = Collections.emptyIterator();
518
+ } else {
519
+ final Node<ValueType, AveragesTo, T> nodeWhereBestMaxSumSubSequenceEnds = nodesOrderedByMaxSum.last();
520
+ final Iterable<Node<ValueType, AveragesTo, T>> nodeIterable = ()->nodeIterator(nodeWhereBestMaxSumSubSequenceEnds.getStartOfMaxSumSubSequenceEndingHere(), nodeWhereBestMaxSumSubSequenceEnds);
521
+ result = Util.map(nodeIterable, node->node.getValue()).iterator();
522
+ }
523
+ return result;
513 524
}
514 525
515 526
@Override
516 527
public Iterator<T> getSubSequenceWithMinSum() {
517
- final Node<ValueType, AveragesTo, T> nodeWhereBestMinSumSubSequenceEnds = nodesOrderedByMinSum.first();
518
- final Iterable<Node<ValueType, AveragesTo, T>> nodeIterable = ()->nodeIterator(nodeWhereBestMinSumSubSequenceEnds.getStartOfMinSumSubSequenceEndingHere(), nodeWhereBestMinSumSubSequenceEnds);
519
- return Util.map(nodeIterable, node->node.getValue()).iterator();
528
+ final Iterator<T> result;
529
+ if (isEmpty()) {
530
+ result = Collections.emptyIterator();
531
+ } else {
532
+ final Node<ValueType, AveragesTo, T> nodeWhereBestMinSumSubSequenceEnds = nodesOrderedByMinSum.first();
533
+ final Iterable<Node<ValueType, AveragesTo, T>> nodeIterable = ()->nodeIterator(nodeWhereBestMinSumSubSequenceEnds.getStartOfMinSumSubSequenceEndingHere(), nodeWhereBestMinSumSubSequenceEnds);
534
+ result = Util.map(nodeIterable, node->node.getValue()).iterator();
535
+ }
536
+ return result;
520 537
}
521 538
522 539
@Override