3c8830997485508672d5020cc5c0e45b11fb96a1
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 | +} |