From c2285cd7b7367bf85e56ca70fb94a2c885dde715 Mon Sep 17 00:00:00 2001 From: brurucy Date: Wed, 6 Apr 2022 10:52:45 +0300 Subject: [PATCH] Added the Spine Multiset --- .../cs/dsg/confcheck/ConformanceChecker.java | 16 +- .../confcheck/RandomConformanceChecker.java | 55 +- .../java/ee/ut/cs/dsg/confcheck/Runner.java | 77 ++- .../StatefulRandomConformanceChecker.java | 24 +- src/main/java/ee/ut/cs/dsg/spine/Spine.java | 565 ++++++++++++++++++ 5 files changed, 644 insertions(+), 93 deletions(-) create mode 100644 src/main/java/ee/ut/cs/dsg/spine/Spine.java diff --git a/src/main/java/ee/ut/cs/dsg/confcheck/ConformanceChecker.java b/src/main/java/ee/ut/cs/dsg/confcheck/ConformanceChecker.java index 85de78c..1851e3d 100644 --- a/src/main/java/ee/ut/cs/dsg/confcheck/ConformanceChecker.java +++ b/src/main/java/ee/ut/cs/dsg/confcheck/ConformanceChecker.java @@ -1,10 +1,8 @@ package ee.ut.cs.dsg.confcheck; import ee.ut.cs.dsg.confcheck.alignment.Alignment; -import ee.ut.cs.dsg.confcheck.alignment.Move; -import ee.ut.cs.dsg.confcheck.cost.CostFunction; import ee.ut.cs.dsg.confcheck.trie.Trie; -import ee.ut.cs.dsg.confcheck.trie.TrieNode; +import ee.ut.cs.dsg.spine.Spine; import java.util.*; @@ -12,7 +10,7 @@ public abstract class ConformanceChecker { protected final Trie modelTrie; protected final int logMoveCost ; protected final int modelMoveCost ; - protected PriorityQueue nextChecks; + protected Spine nextChecks; protected int cntr=1; protected int maxStatesInQueue; @@ -56,7 +54,7 @@ public ConformanceChecker(Trie modelTrie, int logCost, int modelCost, int maxSta states = new ArrayList<>(); this.maxStatesInQueue = maxStatesInQueue; - nextChecks = new PriorityQueue<>(maxStatesInQueue); + nextChecks = new Spine<>(maxStatesInQueue); // this.seenBefore = new HashSet<>(); } @@ -265,7 +263,7 @@ protected void addStateToTheQueue(State state, State candidateState) { if ((state.getAlignment().getTotalCost() + Math.min(Math.abs(state.getTracePostfix().size() - state.getNode().getMinPathLengthToEnd()),Math.abs(state.getTracePostfix().size() - state.getNode().getMaxPathLengthToEnd())))< candidateState.getAlignment().getTotalCost())// && state.getNode().getLevel() > candidateState.getNode().getLevel()) { - nextChecks.add(state); + nextChecks.push(state); // states.add(state); } else { @@ -276,7 +274,7 @@ protected void addStateToTheQueue(State state, State candidateState) { } else //if (state.getCostSoFar()< (nextChecks.size() == 0? Integer.MAX_VALUE: nextChecks.peek().getCostSoFar())) { - nextChecks.add(state); + nextChecks.push(state); // states.add(state); } if (cntr % cleanseFrequency == 0) @@ -290,7 +288,7 @@ private void cleanState(State candidateState) int coundDown=cleanseFrequency; State current; while (nextChecks.size() > cleanseFrequency & coundDown > 0) { - current = nextChecks.poll(); + current = nextChecks.pollFirst(); if (candidateState != null) { if ((current.getAlignment().getTotalCost() + Math.abs(current.getTracePostfix().size() - current.getNode().getMinPathLengthToEnd())) >= candidateState.getAlignment().getTotalCost())// && state.getNode().getLevel() > candidateState.getNode().getLevel()) @@ -302,7 +300,7 @@ private void cleanState(State candidateState) } } else { - nextChecks.add(new State(current.getAlignment(), current.getTracePostfix(), current.getNode(), (int) (current.getCostSoFar() + (1 + 10)))); + nextChecks.push(new State(current.getAlignment(), current.getTracePostfix(), current.getNode(), (int) (current.getCostSoFar() + (1 + 10)))); coundDown--; } diff --git a/src/main/java/ee/ut/cs/dsg/confcheck/RandomConformanceChecker.java b/src/main/java/ee/ut/cs/dsg/confcheck/RandomConformanceChecker.java index d25714e..3439e87 100644 --- a/src/main/java/ee/ut/cs/dsg/confcheck/RandomConformanceChecker.java +++ b/src/main/java/ee/ut/cs/dsg/confcheck/RandomConformanceChecker.java @@ -10,9 +10,6 @@ import java.util.*; public class RandomConformanceChecker extends ConformanceChecker{ - - - protected int exploitVersusExploreFrequency = 113; protected int numEpochs; protected boolean onMatchFollowPrefixOnly = false; @@ -55,29 +52,22 @@ protected State pickRandom() State s; if (cntr % exploitVersusExploreFrequency== 0) { -// long start = System.currentTimeMillis(); - State[] elements = new State[nextChecks.size()]; - nextChecks.toArray(elements); - - - - - +// long start = System.currentTimeMillis() int upperBound = nextChecks.size(); int lowerBound = Math.max(upperBound- (nextChecks.size()/2)-1,1); index = rnd.nextInt( lowerBound); - s = elements[(lowerBound*whichDirection)+index >= upperBound? 0:(lowerBound*whichDirection)+index]; + s = nextChecks.get((lowerBound*whichDirection)+index >= upperBound? 0:(lowerBound*whichDirection)+index); // s = states.remove((lowerBound*whichDirection)+index >= upperBound? 0:(lowerBound*whichDirection)+index); whichDirection = whichDirection == 0 ? 1:0; - nextChecks.remove(s); + nextChecks.delete(s); cntr = 0; } else { - s = nextChecks.poll(); + s = nextChecks.pollFirst(); states.remove(s); } @@ -88,17 +78,17 @@ protected void successiveHalving() { // if (nextChecks.size() < 100000) // return ; - List result = new ArrayList<>(nextChecks.size()/2); - State[] elements = new State[nextChecks.size()]; - nextChecks.toArray(elements); - - Arrays.sort(elements); - int quantile = nextChecks.size()/4; - nextChecks.clear(); - for(int i = 0; i < quantile*4; i+=2) - { - nextChecks.add(elements[i]); - } +// List result = new ArrayList<>(nextChecks.size()/2); +// State[] elements = new State[nextChecks.size()]; +// nextChecks.toArray(elements); +// +// Arrays.sort(elements); +// int quantile = nextChecks.size()/4; +// nextChecks.clear(); +// for(int i = 0; i < quantile*4; i+=2) +// { +// nextChecks.add(elements[i]); +// } // for (int i = quantile*2; i < quantile*3; i++) // { // nextChecks.add(elements[i]); @@ -127,7 +117,7 @@ public Alignment check(List trace) List traceSuffix; State state = new State(alg,trace, modelTrie.getRoot(),0); - nextChecks.add(state); + nextChecks.push(state); State candidateState = null; String event; @@ -275,7 +265,6 @@ else if (alg.getTotalCost() < candidateState.getAlignment().getTotalCost()) // if(!onMatchFollowPrefixOnly) { - List trSuffix = new LinkedList<>(); trSuffix.addAll(traceSuffix); State nonSyncState = new State(alg, trSuffix, node.getParent(),0); @@ -312,8 +301,6 @@ else if (alg.getTotalCost() < candidateState.getAlignment().getTotalCost()) syncState = new State(alg,traceSuffix,prev,cost); addStateToTheQueue(syncState, candidateState); - - } // On 27th of May 2021. we need to give the option to a log move as well as a model move else // there is no match, we have to make the model move and the log move @@ -355,13 +342,13 @@ protected void addStateToTheQueue(State state, State candidateState) { { // if (verbose) // System.out.println("Max queue size reached. New state is not added!"); - if (state.getCostSoFar() < nextChecks.peek().getCostSoFar()) + if (state.getCostSoFar() < nextChecks.peekFirst().getCostSoFar()) // if (state.getAlignment().getTotalCost() < nextChecks.peek().getAlignment().getTotalCost()) { // System.out.println(String.format("Adding a good candidate whose cost is %d which is less that the least cost so far %d", state.getAlignment().getTotalCost(), nextChecks.peek().getAlignment().getTotalCost())); // System.out.println(String.format("Replacement state suffix length %d, number of model moves %d", state.getTracePostfix().size(), state.getNode().getLevel())); - nextChecks.poll(); - nextChecks.add(state); + nextChecks.pollFirst(); + nextChecks.push(state); // states.add(state); } return; @@ -370,7 +357,7 @@ protected void addStateToTheQueue(State state, State candidateState) { if ((state.getAlignment().getTotalCost()+Math.min(Math.abs(state.getTracePostfix().size() - state.getNode().getMinPathLengthToEnd()),Math.abs(state.getTracePostfix().size() - state.getNode().getMaxPathLengthToEnd()))) < candidateState.getAlignment().getTotalCost())// && state.getNode().getLevel() > candidateState.getNode().getLevel()) { - nextChecks.add(state); + nextChecks.push(state); // states.add(state); } else if (verbose) { @@ -382,7 +369,7 @@ else if (verbose) { } else //if (state.getCostSoFar()< (nextChecks.size() == 0? Integer.MAX_VALUE: nextChecks.peek().getCostSoFar())) // { - nextChecks.add(state); + nextChecks.push(state); // states.add(state); // } diff --git a/src/main/java/ee/ut/cs/dsg/confcheck/Runner.java b/src/main/java/ee/ut/cs/dsg/confcheck/Runner.java index 30851c7..8511ffa 100644 --- a/src/main/java/ee/ut/cs/dsg/confcheck/Runner.java +++ b/src/main/java/ee/ut/cs/dsg/confcheck/Runner.java @@ -62,52 +62,49 @@ public static void main(String... args) // testBed3(); // System.exit(0); - String randomProxyLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2015\\randomLog.xml"; - String clusteredLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2015\\sampledClusteredLog.xml"; - String simulatedLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2015\\simulatedLog.xml"; - String reducedActivityLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2015\\reducedLogActivity.xml"; - String frequencyActivityLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2015\\frequencyLog.xml"; - String sampleLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2015\\sampledLog.xml"; - String singular = "C:\\Work\\DSG\\Data\\Logs\\BPI2015\\Singular.xes"; - - String randomSepsisProxyLog = "C:\\Work\\DSG\\Data\\Logs\\Sepsis\\randomLog.xml"; - String clusteredSepsisLog = "C:\\Work\\DSG\\Data\\Logs\\Sepsis\\sampledClusteredLog.xml"; - String simulatedSepsisLog = "C:\\Work\\DSG\\Data\\Logs\\Sepsis\\simulatedLog.xml"; - String frequencySepsisLog = "C:\\Work\\DSG\\Data\\Logs\\Sepsis\\frequencyLog.xml"; - String reducedSepsisActivityLog = "C:\\Work\\DSG\\Data\\Logs\\Sepsis\\reducedLogActivity.xml"; - String sampleSepsisLog = "C:\\Work\\DSG\\Data\\Logs\\Sepsis\\sampledLog.xml"; + String randomProxyLog = "./BPI2015/randomLog.xml"; + String clusteredLog = "./BPI2015/sampledClusteredLog.xml"; + String simulatedLog = "./BPI2015/simulatedLog.xml"; + String reducedActivityLog = "./BPI2015/reducedLogActivity.xml"; + String frequencyActivityLog = "./BPI2015/frequencyLog.xml"; + String sampleLog = "./BPI2015/sampledLog.xml"; + String singular = "./BPI2015/Singular.xes"; + + String randomSepsisProxyLog = "./Sepsis/randomLog.xml"; + String clusteredSepsisLog = "./Sepsis/sampledClusteredLog.xml"; + String simulatedSepsisLog = "./Sepsis/simulatedLog.xml"; + String frequencySepsisLog = "./Sepsis/frequencyLog.xml"; + String reducedSepsisActivityLog = "./Sepsis/reducedLogActivity.xml"; + String sampleSepsisLog = "./Sepsis/sampledLog.xml"; // BPI 2019 - String originalLog2019 = "C:\\Work\\DSG\\Data\\Logs\\BPI2019\\BPI_Challenge_2019.xml"; - String random2019ProxyLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2019\\randomLog.xml"; - String clustered2019Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2019\\sampledClusteredLog.xml"; - String simulated2019Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2019\\simulatedLog.xml"; - String reduced2019ActivityLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2019\\reducedLogActivity.xml"; - String sample2019Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2019\\sampledLog.xml"; - String frequency2019Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2019\\frequencyLog.xml"; + String originalLog2019 = "./BPI2019/BPI_Challenge_2019.xml"; + String random2019ProxyLog = "./BPI2019/randomLog.xml"; + String clustered2019Log = "./BPI2019/sampledClusteredLog.xml"; + String simulated2019Log = "./BPI2019/simulatedLog.xml"; + String reduced2019ActivityLog = "./BPI2019/reducedLogActivity.xml"; + String sample2019Log = "./BPI2019/sampledLog.xml"; + String frequency2019Log = "./BPI2019/frequencyLog.xml"; // BPI 2012 - String originalLog2012 = "C:\\Work\\DSG\\Data\\Logs\\BPI2012\\BPIC2012.xes"; - String random2012ProxyLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2012\\randomLog.xml"; - String clustered2012Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2012\\sampledClusteredLog.xml"; - String simulated2012Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2012\\simulatedLog.xml"; - String reduced2012ActivityLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2012\\reducedLogActivity.xml"; - String sample2012Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2012\\sampledLog.xml"; - String frequency2012Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2012\\frequencyLog.xml"; + String originalLog2012 = "./BPI2012/BPIC2012.xes"; + String random2012ProxyLog = "./BPI2012/randomLog.xml"; + String clustered2012Log = "./BPI2012/sampledClusteredLog.xml"; + String simulated2012Log = "./BPI2012/simulatedLog.xml"; + String reduced2012ActivityLog = "./BPI2012/reducedLogActivity.xml"; + String sample2012Log = "./BPI2012/sampledLog.xml"; + String frequency2012Log = "./BPI2012/frequencyLog.xml"; // BPI 2017 - String originalLog2017 = "C:\\Work\\DSG\\Data\\Logs\\BPI2017\\BPIC2017.xes.xes"; - String random2017ProxyLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2017\\rand_randomLog.xml"; - String clustered2017Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2017\\sampledClusteredLog.xml"; - String simulated2017Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2017\\simulatedLog.xml"; - String reduced2017ActivityLog = "C:\\Work\\DSG\\Data\\Logs\\BPI2017\\reducedLogActivity.xml"; - String sample2017Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2017\\sampledLog.xml"; - String frequency2017Log = "C:\\Work\\DSG\\Data\\Logs\\BPI2017\\freq_frequencyLog.xml"; - - - - - testOnConformanceApproximationResults(frequencyActivityLog, sampleLog, ConformanceCheckerType.TRIE_RANDOM_STATEFUL, LogSortType.TRACE_LENGTH_ASC ); + String originalLog2017 = "./BPI2017/BPIC2017.xes.xes"; + String random2017ProxyLog = "./BPI2017/rand_randomLog.xml"; + String clustered2017Log = "./BPI2017/sampledClusteredLog.xml"; + String simulated2017Log = "./BPI2017/simulatedLog.xml"; + String reduced2017ActivityLog = "./BPI2017/reducedLogActivity.xml"; + String sample2017Log = "./BPI2017/sampledLog.xml"; + String frequency2017Log = "./BPI2017/freq_frequencyLog.xml"; + + testOnConformanceApproximationResults(clusteredLog, sampleLog, ConformanceCheckerType.TRIE_RANDOM_STATEFUL, LogSortType.TRACE_LENGTH_ASC ); // // BPI 2015 diff --git a/src/main/java/ee/ut/cs/dsg/confcheck/StatefulRandomConformanceChecker.java b/src/main/java/ee/ut/cs/dsg/confcheck/StatefulRandomConformanceChecker.java index 881ac44..9aa920d 100644 --- a/src/main/java/ee/ut/cs/dsg/confcheck/StatefulRandomConformanceChecker.java +++ b/src/main/java/ee/ut/cs/dsg/confcheck/StatefulRandomConformanceChecker.java @@ -7,6 +7,7 @@ import ee.ut.cs.dsg.confcheck.trie.TrieNode; import ee.ut.cs.dsg.confcheck.util.Configuration; import ee.ut.cs.dsg.confcheck.util.Utils; +import ee.ut.cs.dsg.spine.Spine; import java.util.*; @@ -25,7 +26,7 @@ public StatefulRandomConformanceChecker(Trie trie, int logCost, int modelCost, i } private int trialsPerEvent; - private final Map> searchSpace; + private final Map> searchSpace; private final boolean reuseSearchSpace =false; public void setTrialsPerEvent(int tpe) @@ -42,11 +43,14 @@ private State getStateFromTracesTrie(List trace) List tracePrefix = trace.subList(0,node.getLevel()); // fill the queue based on the state if (reuseSearchSpace) { - PriorityQueue previousSearchSpace = searchSpace.get(node.getAlignmentState()); + Spine previousSearchSpace = searchSpace.get(node.getAlignmentState()); if (previousSearchSpace != null) { // previousSearchSpace.stream().filter(ps -> isAValidState(ps, trace)).forEach(vs -> nextChecks.add(vs)); - previousSearchSpace.stream().filter(ps -> isAValidState(ps, trace)).forEach(vs -> nextChecks.add( - new State(vs.getAlignment(), trace.subList(vs.getNode().getLevel(),trace.size()),vs.getNode(),vs.getCostSoFar()))); + for (State state: previousSearchSpace) { + if (isAValidState(state, trace)) { + nextChecks.push(new State(state.getAlignment(), trace.subList(state.getNode().getLevel(),trace.size()),state.getNode(),state.getCostSoFar())); + } + } if (verbose) System.out.printf("Loading previous search space to resume from. Kept %d states from original %d states%n", nextChecks.size(), previousSearchSpace.size()); } @@ -82,7 +86,7 @@ public Alignment check(List trace) } - nextChecks.add(state); + nextChecks.push(state); State candidateState = null; String event; @@ -378,8 +382,6 @@ private void updateTracesTrie(final State candidateState) { int stateSize = nextChecks.size(); - - Stack statesBack = new Stack(); State currentState = candidateState.getParentState(); int alignmentSize = currentState.getAlignment().getMoves().size(); @@ -388,11 +390,13 @@ private void updateTracesTrie(final State candidateState) { if (reuseSearchSpace && stateSize > 0) { - PriorityQueue currentSearchSpace = new PriorityQueue<>(); - currentSearchSpace.addAll(nextChecks); + Spine currentSearchSpace = new Spine<>(1024); + for (State check:nextChecks) { + currentSearchSpace.push(check); + } // PriorityQueue currentSearchSpace = nextChecks; // currentSearchSpace.addAll(prevStates); - currentSearchSpace.add(currentState); + currentSearchSpace.push(currentState); searchSpace.put(currentState,currentSearchSpace); } diff --git a/src/main/java/ee/ut/cs/dsg/spine/Spine.java b/src/main/java/ee/ut/cs/dsg/spine/Spine.java new file mode 100644 index 0000000..c2105a3 --- /dev/null +++ b/src/main/java/ee/ut/cs/dsg/spine/Spine.java @@ -0,0 +1,565 @@ +// GPL License. +// Copyright: brurucy at protonmail dot ch +package ee.ut.cs.dsg.spine; + +import java.util.ArrayList; +import java.util.Iterator; + +class Cord { + public int[] innerStructure; + private int mostSignificantBit(int key) { + int result = key; + + result |= result >> 1; + result |= result >> 2; + result |= result >> 4; + result |= result >> 8; + result |= result >> 16; + result |= result >> 32; + + return result - (result >> 1); + } + + private int leastSignificantBit(int key) { + return key & -key; + } + + public Cord(int[] lengthArray) { + int length = lengthArray.length; + this.innerStructure = new int[length]; + this.innerStructure[0] = lengthArray[0]; + for (int i = 1; i < length; i++) { + this.innerStructure[i] = this.innerStructure[i - 1] + lengthArray[i]; + } + for (int i = length - 1; i > 0; i--) { + int lowerBound = (i & (i + 1)) - 1; + if (lowerBound >= 0) { + this.innerStructure[i] -= this.innerStructure[lowerBound]; + } + } + } + + public Cord(ArrayList> spine) { + int length = spine.size(); + this.innerStructure = new int[length]; + this.innerStructure[0] = spine.get(0).size(); + for (int i = 1; i < length; i++) { + this.innerStructure[i] = this.innerStructure[i - 1] + spine.get(i).size(); + } + for (int i = length - 1; i > 0; i--) { + int lowerBound = (i & (i + 1)) - 1; + if (lowerBound >= 0) { + this.innerStructure[i] -= this.innerStructure[lowerBound]; + } + } + } + + public int prefixSum(int ith) { + int sum = 0; + int i = ith; + while (i > 0) { + sum += this.innerStructure[i - 1]; + i &= i - 1; + } + return sum; + } + + public void increaseLength(int ith) { + int length = this.innerStructure.length; + int i = ith; + while (i < length) { + this.innerStructure[i] += 1; + i |= i + 1; + } + + } + + public void decreaseLength(int ith) { + int length = this.innerStructure.length; + int i = ith; + while (i < length) { + this.innerStructure[i] -= 1; + i |= i + 1; + } + } + + public int indexOf(int ith) { + int length = this.innerStructure.length; + int high = mostSignificantBit(length) * 2; + int mid = 0; + + while (high != mid) { + int lsb = leastSignificantBit(high); + if (high <= length && this.innerStructure[high - 1] <= ith) { + ith -= this.innerStructure[high - 1]; + mid = high; + high += lsb / 2; + } else { + high += lsb / 2 - lsb; + } + } + return mid; + } +} + +class Vertebra extends ArrayList { + public T max; + + public Vertebra(int vertebraSize) { + this.ensureCapacity(vertebraSize * 2); + } + + public int lowerBound(T key) { + int low = 0; + int mid; + int high = this.size(); + + while (low < high) { + mid = (low + high) >>> 1; + Comparable midVal = (Comparable) this.get(mid); + int cmp = midVal.compareTo(key); + if (cmp < 0) + low = mid + 1; + else + high = mid; + } + return low; + } + + public int upperBound(T key) { + int low = 0; + int mid; + int high = this.size(); + + while (low < high) { + mid = (low + high) >>> 1; + Comparable midVal = (Comparable) this.get(mid); + int cmp = midVal.compareTo(key); + if (cmp > 0) + high = mid; + else + low = mid + 1; + } + return low; + } + + public boolean has(T key) { + int lowerBound = this.lowerBound(key); + int upperBound = this.upperBound(key); + + if (lowerBound >= this.size()) { + return false; + } + + for (int i = lowerBound; i < upperBound; i++) { + if (this.get(i).equals(key)) + return true; + } + + return false; + } + + @Override + public boolean add(T key) { + int position = this.lowerBound(key); + if (position >= this.size()) { + this.max = key; + return super.add(key); + } else { + Comparable comparableKey = (Comparable) key; + this.add(position, key); + int maxCmp = comparableKey.compareTo(this.max); + if (maxCmp > 0) { + this.max = key; + } + return true; + } + } + + public boolean delete(T key) { + int lowerBound = this.lowerBound(key); + int upperBound = this.upperBound(key); + + if (lowerBound >= this.size()) { + return false; + } + + for (int i = lowerBound; i < upperBound; i++) { + if (this.get(i).equals(key)) { + this.remove(i); + return true; + } + } + + return false; + } + +} + +class IntegerTuple { + public int k; + public int v; + + public IntegerTuple(int k, int v) { + this.k = k; + this.v = v; + } +} + +class SpineIterator> implements Iterator { + int currentX; + int currentY; + int remaining; + Spine spine; + + public SpineIterator(Spine spine) { + this.currentX = 0; + this.currentY = 0; + this.spine = spine; + this.remaining = spine.size() - 1; + } + + public boolean hasNext() { + return this.remaining > 0; + } + + public T next() { + Vertebra currentVertebra = this.spine.vertebrae.get(this.currentX); + T currentValue = currentVertebra.get(this.currentY); + if (this.currentY == currentVertebra.size() - 1) { + if (this.currentX == this.spine.vertebrae.size() - 1) { + this.remaining = 0; + } else { + this.currentY = 0; + this.currentX += 1; + } + } else { + this.currentY += 1; + } + this.remaining -= 1; + return currentValue; + } +} + +/* Spine is a sorted set data structure. It is a one-level B-Tree without any pointers. + * */ +public class Spine> implements Iterable { + public ArrayList> vertebrae; + public int vertebraSize; + private Cord cord; + private int length; + + /* Initializes the spine with a vertebra size. + * @param vertebraSize should be set to 1024 unless you have a *very* good reason. + * */ + public Spine(int vertebraSize) { + int[] emptyArray = {0}; + this.cord = new Cord(emptyArray); + this.vertebrae = new ArrayList<>(); + this.vertebrae.add(new Vertebra<>(vertebraSize)); + this.vertebraSize = vertebraSize; + this.length = 0; + } + + public void clear() { + int[] emptyArray = {0}; + this.cord = new Cord(emptyArray); + this.vertebrae = new ArrayList<>(); + this.vertebrae.add(new Vertebra<>(vertebraSize)); + this.length = 0; + } + + public Iterator iterator() { + return new SpineIterator<>(this); + } + + public int size() { + return this.length; + } + + private void buildCord() { + this.cord = new Cord(this.vertebrae); + } + + private void balance(int firstLevelIndex) { + Vertebra targetVertebra = this.vertebrae.get(firstLevelIndex); + int firstLevelIndexLength = targetVertebra.size(); + int half = firstLevelIndexLength / 2; + Vertebra newVertebra = new Vertebra<>(this.vertebraSize); + Vertebra oldVertebra = new Vertebra<>(this.vertebraSize); + for (int i = 0; i < firstLevelIndexLength; i++) { + if (i < half) { + oldVertebra.add(targetVertebra.get(i)); + } else { + newVertebra.add(targetVertebra.get(i)); + } + } + this.vertebrae.set(firstLevelIndex, oldVertebra); + this.vertebrae.add(firstLevelIndex + 1, newVertebra); + this.buildCord(); + } + + private int spineLowerbound(T key) { + int low = 0; + int mid; + int high = this.vertebrae.size(); + + while (low < high) { + mid = (low + high) >>> 1; + Comparable midVal = this.vertebrae.get(mid).max; + int cmp = midVal.compareTo(key); + if (cmp < 0) + low = mid + 1; + else + high = mid; + } + return low; + } + + private int spineUpperbound(T key) { + int low = 0; + int mid; + int high = this.vertebrae.size(); + + while (low < high) { + mid = (low + high) >>> 1; + Comparable midVal = this.vertebrae.get(mid).max; + int cmp = midVal.compareTo(key); + if (cmp > 0) + high = mid; + else + low = mid + 1; + } + return low; + } + + /* Adds a key of type T and retains sortedness. + * @param key the key to be inserted. + * @return true if the underlying collection was changed or false otherwise. + * */ + public void push(T key) { + if (this.length == 0) { + this.vertebrae.get(0).add(key); + this.cord.increaseLength(0); + this.length += 1; + return; + } + int vertebraIndex = this.spineLowerbound(key); + if (vertebraIndex >= this.vertebrae.size()) { + vertebraIndex -= 1; + } + boolean extended = this.vertebrae.get(vertebraIndex).add(key); + if (!extended) { + return; + } + if (this.vertebrae.get(vertebraIndex).size() > this.vertebraSize) { + this.balance(vertebraIndex); + } else { + this.cord.increaseLength(vertebraIndex); + } + this.length += 1; + } + + public void pushAll(Iterable iterable) { + for (T i : iterable) { + this.push(i); + } + } + + /* Deletes a key ensuring sortedness. + * @param key the key to be removed. + * @return true if the underlying collection was changed or false otherwise. + * */ + public boolean delete(T key) { + if (this.length == 0) { + return false; + } + int lowerBound = this.spineLowerbound(key); + int upperBound = this.spineUpperbound(key); + if (lowerBound >= this.vertebrae.size()) { + return false; + } + boolean shrink = false; + int removalLocation = 0; + if (lowerBound == upperBound - 1 || lowerBound == upperBound) { + if (upperBound + 1 < this.vertebrae.size() && this.vertebrae.get(lowerBound).max.compareTo(key) == 0) { + if (this.vertebrae.get(lowerBound).delete(key)) { + shrink = true; + removalLocation = lowerBound; + } else if (this.vertebrae.get(upperBound).delete(key)) { + shrink = true; + removalLocation = upperBound; + } + } else { + if (this.vertebrae.get(lowerBound).delete(key)) { + shrink = true; + removalLocation = lowerBound; + } + } + } else { + for (int i = lowerBound; i < upperBound; i++) { + if (this.vertebrae.get(i).delete(key)) { + shrink = true; + removalLocation = i; + break; + } + } + } + if (shrink) { + this.cord.decreaseLength(removalLocation); + if (this.vertebrae.get(removalLocation).size() == 0) { + if (this.length != 1) { + this.vertebrae.remove(removalLocation); + } + this.cord = new Cord(this.vertebrae); + } + this.length -= 1; + return true; + } + return false; + } + + private IntegerTuple locate(int ith) { + if (ith >= this.length || ith < 0) { + return new IntegerTuple(-1, -1); + } + int vertebraIndex = this.cord.indexOf(ith); + int offset = 0; + if (vertebraIndex != 0) { + offset = this.cord.prefixSum(vertebraIndex); + } + return new IntegerTuple(vertebraIndex, ith - offset); + } + + /* Deletes the ith element. + * @param ith + * @return the deleted element, null otherwise. + * */ + public T deleteByIndex(int ith) { + if (this.length == 0 || ith >= this.length) { + return null; + } + IntegerTuple indexes = this.locate(ith); + int spineIndex = indexes.k; + int vertebraIndex = indexes.v; + if (spineIndex == -1 || vertebraIndex == -1) { + return null; + } + T out = this.vertebrae.get(spineIndex).get(vertebraIndex); + this.vertebrae.get(spineIndex).remove(vertebraIndex); + this.cord.decreaseLength(vertebraIndex); + if (this.vertebrae.get(spineIndex).size() == 0) { + if (this.length != 1) { + this.vertebrae.remove(spineIndex); + } + this.cord = new Cord(this.vertebrae); + } + this.length -= 1; + return out; + } + + /* Gets the ith element. + * @param ith + * @return the ith element, null otherwise. + * */ + public T get(int ith) { + if (this.length == 0 || ith >= this.length) { + return null; + } + IntegerTuple indexes = this.locate(ith); + int spineIndex = indexes.k; + int vertebraIndex = indexes.v; + return this.vertebrae.get(spineIndex).get(vertebraIndex); + } + + /* Asserts the existence of some key. + * @param key the key whose existence within the container is in question. + * @return true if it exists, false otherwise. + * */ + public boolean has(T key) { + if (this.length == 0) { + return false; + } + int lowerBound = this.spineLowerbound(key); + int upperBound = this.spineUpperbound(key); + if (lowerBound >= this.vertebrae.size()) { + return false; + } + if (lowerBound == upperBound - 1 || lowerBound == upperBound) { + Vertebra container = this.vertebrae.get(lowerBound); + if (upperBound + 1 < this.vertebrae.size() && container.max.compareTo(key) == 0) { + return container.has(key) || this.vertebrae.get(upperBound).has(key); + } else { + return container.has(key); + } + } else { + for (int i = lowerBound; i < upperBound; i++) { + if (this.vertebrae.get(i).has(key)) { + return true; + } + } + } + return false; + } + + /* Removes and returns the smallest element + * @return the element in question or null. + * */ + public T pollFirst() { + if (this.length == 0) { + return null; + } + T out = this.vertebrae.get(0).remove(0); + this.cord.decreaseLength(0); + if (this.vertebrae.get(0).size() == 0) { + if (this.length != 1) { + this.vertebrae.remove(0); + } + this.cord = new Cord(this.vertebrae); + } + this.length -= 1; + return out; + } + + /* Returns the largest element + * @return the element in question or null + * */ + public T peekFirst() { + if (this.length == 0) { + return null; + } + return this.vertebrae.get(0).get(0); + } + + /* Removes and returns the largest element + * @return the element in question or null + * */ + public T pollLast() { + if (this.length == 0) { + return null; + } + int vertebraeSize = vertebrae.size(); + int lastVertebraSize = this.vertebrae.get(vertebraeSize - 1).size(); + T out = this.vertebrae.get(vertebraeSize - 1).remove(lastVertebraSize - 1); + this.cord.decreaseLength(vertebraeSize - 1); + if (this.vertebrae.get(vertebraeSize - 1).size() == 0) { + if (this.length != 1) { + this.vertebrae.remove(vertebraeSize - 1); + } + this.cord = new Cord(this.vertebrae); + } else { + this.vertebrae.get(vertebraeSize - 1).max = this.vertebrae.get(vertebraeSize - 1).get(lastVertebraSize - 2); + } + this.length -= 1; + return out; + } + + public T peekLast() { + if (this.length == 0) { + return null; + } + int vertebraeSize = vertebrae.size(); + int lastVertebraSize = this.vertebrae.get(vertebraeSize - 1).size(); + return this.vertebrae.get(vertebraeSize - 1).get(lastVertebraSize - 1); + } +}