35da2ba1bbeaf9b421a2337ad619dd37abfcb4fd
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 |