package io.nextop.sortedlist;
import com.google.common.collect.Ordering;
import javax.annotation.Nullable;
import java.util.AbstractList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
/** Random-access data structure that maintains
* elements in sorted order, with some extra sorted lookup functionality.
* Inserting by index {@link #add(int, Object)} is not supported,
* since the index is determined by the sort order.
* add/get/remove operations are logarithmic.
*
* Implemented as a splay tree with a sub-tree counter on each node,
* using top-down splaying. The splaying algorithm uses a custom
* modification to maintain the sub-tree counters per node.
*
* Because of splaying, "get"s give faster access to
* recently accessed values/indexes,
* or values/indexes near them.
*
* Based on notes:
* see CLR
* see ftp://ftp.cs.cmu.edu/usr/ftp/usr/sleator/splaying/SplayTree.java
*/
// FIXME inserting in sequential order bad perf
// FIXME support duplicates
// FIXME implement SortedList API correctly
// FIXME (iterator an all ops should splay)
public final class SplaySortedList<E> extends AbstractList<E> implements SortedList<E> {
private final Comparator<? super E> comparator;
private @Nullable Node<E> root;
/* for splaying */
private final Node<E> header = new Node<E>(null);
@SuppressWarnings("unchecked")
public SplaySortedList() {
this((Comparator<? super E>) Ordering.<Comparable<E>>natural());
}
public SplaySortedList(Comparator<? super E> comparator) {
this.comparator = comparator;
}
/** Does not splay. */
E search(int index) {
if (null == root || index < 0 || root.count <= index)
throw new IndexOutOfBoundsException();
Node<E> y = root;
for (int c; 0 != (c = index - (null != y.left ? y.left.count : 0));) {
if (c < 0) {
y = y.left;
} else {
index -= 1 + (null != y.left ? y.left.count : 0);
y = y.right;
}
}
return y.value;
}
FindResult<E> search(final E value) {
return search(comparable(value, comparator));
}
/** Finds the value that matches, or the values around,
* the given query. Does not splay.
* @param q must have an implied ordering of the internal
* comparator, but can be based on a different object
* representation than E. e.g. E may be a complex object
* (A, B, ...), and <code>op</code> can be a prefix (A, ...),
* or some order implied on other fields of E.*/
FindResult<E> search(Comparable<? super E> q) {
if (null == root) {
return new FindResult<E>(null, -1, 1);
}
int index = 0;
Node<E> y = root;
int c;
while (0 != (c = q.compareTo(y.value))) {
if (c < 0) {
if (null == y.left)
break;
y = y.left;
} else {
if (null == y.right)
break;
index += 1 + (null != y.left ? y.left.count : 0);
y = y.right;
}
}
index += null != y.left ? y.left.count : 0;
// c is q.compare(y.value)
return new FindResult<E>(y.value, index, c);
}
/////// SPLAYING ///////
// FIXME have a version of splay that uses a comparator
private void splay(E value) {
Node<E> l, r, t, y;
l = r = header;
t = root;
header.left = header.right = null;
header.count = 0;
for (int c; 0 != (c = comparator.compare(value, t.value)); ) {
if (c < 0) {
if (null == t.left)
break;
if (comparator.compare(value, t.left.value) < 0) {
// rotate right + preserve counts
y = t.left;
t.left = y.right;
y.right = t;
y.count += 1 + (null != t.right ? t.right.count : 0);
t.count -= 1 + (null != y.left ? y.left.count : 0);
t = y;
if (null == t.left)
break;
}
// link right
r.left = t;
r = t;
t = t.left;
} else {
if (null == t.right)
break;
if (0 < comparator.compare(value, t.right.value)) {
// rotate left + preserve counts
y = t.right;
t.right = y.left;
y.left = t;
y.count += 1 + (null != t.left ? t.left.count : 0);
t.count -= 1 + (null != y.right ? y.right.count : 0);
t = y;
if (null == t.right)
break;
}
// link left
l.right = t;
l = t;
t = t.right;
}
}
// assemble + reset counts
l.right = t.left;
r.left = t.right;
t.left = header.right;
t.right = header.left;
resetLrCounts(t);
t.count = 1 + (null != t.left ? t.left.count : 0) + (null != t.right ? t.right.count : 0);
root = t;
}
private void splay(int index) {
Node<E> l, r, t, y;
l = r = header;
t = root;
header.left = header.right = null;
header.count = 0;
for (int c; 0 != (c = index - (null != t.left ? t.left.count : 0));) {
if (c < 0) {
if (null == t.left)
break;
if (index - (null != t.left.left ? t.left.left.count : 0) < 0) {
// rotate right + preserve counts
y = t.left;
t.left = y.right;
y.right = t;
y.count += 1 + (null != t.right ? t.right.count : 0);
t.count -= 1 + (null != y.left ? y.left.count : 0);
t = y;
if (null == t.left)
break;
}
// link right
r.left = t;
r = t;
t = t.left;
} else {
index -= 1 + (null != t.left ? t.left.count : 0);
if (null == t.right)
break;
if (0 < index - (null != t.right.left ? t.right.left.count : 0)) {
index -= 1 + (null != t.right.left ? t.right.left.count : 0);
// rotate left + preserve counts
y = t.right;
t.right = y.left;
y.left = t;
y.count += 1 + (null != t.left ? t.left.count : 0);
t.count -= 1 + (null != y.right ? y.right.count : 0);
t = y;
if (null == t.right)
break;
}
// link left
l.right = t;
l = t;
t = t.right;
}
}
// assemble + reset counts
l.right = t.left;
r.left = t.right;
t.left = header.right;
t.right = header.left;
resetLrCounts(t);
t.count = 1 + (null != t.left ? t.left.count : 0) + (null != t.right ? t.right.count : 0);
root = t;
}
/** repairs counts on re-linked LR subtrees from the top-down splay.
* The nodes on the paths to the next-lowest
* and next-highest elements did not have the sub-tree counts
* updated correctly. The nodes off that path have
* correct sub-tree counts due to preserving the counts in the rotation steps.
* This algorithm walks the paths to the next-lowest and next-highest
* (from <code>n</code>) and resets the counts, assuming the nodes
* off that path have correct counts.
*/
private void resetLrCounts(Node<E> n) {
Node<E> y;
int c;
// reset counts on left
c = 0;
for (y = n.left; null != y; ) {
c += 1;
if (null != y.right) {
if (null != y.left)
c += y.left.count;
y = y.right;
} else {
y = y.left;
}
}
for (y = n.left; null != y; ) {
y.count = c;
c -= 1;
if (null != y.right) {
if (null != y.left)
c -= y.left.count;
y = y.right;
} else {
y = y.left;
}
}
// reset counts on right
c = 0;
for (y = n.right; null != y; ) {
c += 1;
if (null != y.left) {
if (null != y.right)
c += y.right.count;
y = y.left;
} else {
y = y.right;
}
}
for (y = n.right; null != y; ) {
y.count = c;
c -= 1;
if (null != y.left) {
if (null != y.right)
c -= y.right.count;
y = y.left;
} else {
y = y.right;
}
}
}
/////// SortedList IMPLEMENTATION ///////
@Override
public @Nullable E lower(E value) {
return lower(comparable(value, comparator));
}
@Override
public @Nullable E lower(Comparable<? super E> q) {
FindResult<E> r = search(q);
if (0 < r.c) {
return r.value;
} else if (0 < r.index) {
return get(r.index - 1);
} else {
return null;
}
}
@Override
public int lowerIndex(E value) {
return lowerIndex(comparable(value, comparator));
}
@Override
public int lowerIndex(Comparable<? super E> q) {
FindResult<E> r = search(q);
if (0 < r.c) {
return r.index;
} else {
return r.index - 1;
}
}
@Override
public @Nullable E floor(E value) {
return floor(comparable(value, comparator));
}
@Override
public @Nullable E floor(Comparable<? super E> q) {
FindResult<E> r = search(q);
if (0 <= r.c) {
return r.value;
} else if (0 < r.index) {
return get(r.index - 1);
} else {
return null;
}
}
@Override
public int floorIndex(E value) {
return floorIndex(comparable(value, comparator));
}
@Override
public int floorIndex(Comparable<? super E> q) {
FindResult<E> r = search(q);
if (0 <= r.c) {
return r.index;
} else {
return r.index - 1;
}
}
@Override
public @Nullable E higher(E value) {
return higher(comparable(value, comparator));
}
@Override
public @Nullable E higher(Comparable<? super E> q) {
FindResult<E> r = search(q);
if (r.c < 0) {
return r.value;
} else if (r.index + 1 < size()) {
return get(r.index + 1);
} else {
return null;
}
}
@Override
public int higherIndex(E value) {
return higherIndex(comparable(value, comparator));
}
@Override
public int higherIndex(Comparable<? super E> q) {
FindResult<E> r = search(q);
if (r.c < 0) {
return r.index;
} else {
return r.index + 1;
}
}
@Override
public @Nullable E ceiling(E value) {
return ceiling(comparable(value, comparator));
}
@Override
public @Nullable E ceiling(Comparable<? super E> q) {
FindResult<E> r = search(q);
if (r.c <= 0) {
return r.value;
} else if (r.index + 1 < size()) {
return get(r.index + 1);
} else {
return null;
}
}
@Override
public int ceilingIndex(E value) {
return ceilingIndex(comparable(value, comparator));
}
@Override
public int ceilingIndex(Comparable<? super E> q) {
FindResult<E> r = search(q);
if (r.c <= 0) {
return r.index;
} else {
return r.index + 1;
}
}
/////// SortedList INSERTION IMPLEMENTATION ///////
@Override
public boolean insertAll(Collection<? extends E> values) {
boolean m = false;
for (E value : values) {
m |= insert(value);
}
return m;
}
@Override
public boolean insert(E value) {
if (null == value) {
throw new NullPointerException();
}
try {
if (null == root) {
root = new Node<E>(value);
return true;
}
splay(value);
int c = comparator.compare(value, root.value);
if (0 == c)
return false;
Node<E> n = new Node<E>(value);
n.count += root.count;
if (c < 0) {
n.right = root;
if (null != root.left) {
root.count -= root.left.count;
n.left = root.left;
root.left = null;
}
} else {
n.left = root;
if (null != root.right) {
root.count -= root.right.count;
n.right = root.right;
root.right = null;
}
}
root = n;
return true;
} finally {
assert checkInvariants();
}
}
/////// List IMPLEMENTATION ///////
@Override
public int size() {
return null != root ? root.count : 0;
}
@Override
public boolean contains(Object value) {
if (null == value) {
return false;
}
try {
@SuppressWarnings("unchecked")
int i = search((E) value).c;
return 0 == i;
// if (null == root)
// return false;
// splay((T) value);
// return value.equals(root);
} finally {
assert checkInvariants();
}
}
@Override
public int indexOf(Comparable<? super E> q) {
// FIXME
throw new UnsupportedOperationException();
}
@Override
public int lastIndexOf(Comparable<? super E> q) {
// FIXME
throw new UnsupportedOperationException();
}
@Override
public int indexOf(Object value) {
if (null == value) {
return -1;
}
try {
@SuppressWarnings("unchecked")
FindResult<E> r = search((E) value);
return 0 == r.c ? r.index : -1;
// if (null == root)
// return -1;
// splay((T) value);
// return null != root.left ? root.left.count : 0;
} finally {
assert checkInvariants();
}
}
@Override
public E get(int index) {
try {
if (null == root || index < 0 || root.count <= index)
throw new IndexOutOfBoundsException("" + index);
splay(index);
assert (null != root.left ? root.left.count : 0) == index :
"Expected root index " + index + " but found " + (null != root.left ? root.left.count : 0);
return root.value;
} finally {
assert checkInvariants();
}
}
@Override
public void add(int location, E object) {
throw new UnsupportedOperationException("Inserting by index is not supported in a sorted list.");
}
@Override
public E remove(int index) {
try {
if (null == root || index < 0 || root.count <= index)
throw new IndexOutOfBoundsException();
splay(index);
E value = root.value;
if (null == root.left) {
root = root.right;
} else {
Node<E> t = root.right;
root = root.left;
splay(index);
root.right = t;
if (null != t)
root.count += t.count;
}
return value;
} finally {
assert checkInvariants();
}
}
@Override
public boolean remove(Object value) {
if (null == value) {
throw new NullPointerException();
}
try {
if (null == root)
return false;
splay((E) value);
int c = comparator.compare((E) value, root.value);
if (0 != c)
return false;
if (null == root.left) {
root = root.right;
} else {
Node<E> t = root.right;
root = root.left;
splay((E) value);
root.right = t;
if (null != t)
root.count += t.count;
}
return true;
} finally {
assert checkInvariants();
}
}
@Override
public void clear() {
try {
root = null;
} finally {
assert checkInvariants();
}
}
/////// INVARIANTS ///////
public boolean checkInvariants() {
if (null != root) {
if (size() < 128) {
// uses recursion; only call this for small trees
_checkCount(root);
_checkBst(root);
} else {
// FIXME these checks can be done by traversing the tree and maintaining state
// FIXME but need to implement the iterator (also a TODO above)
}
}
return true;
}
private int _checkCount(Node<E> n) {
int expectedCount = 1;
if (null != n.left)
expectedCount += _checkCount(n.left);
if (null != n.right)
expectedCount += _checkCount(n.right);
assert expectedCount == n.count : String.format("%d <> %d", expectedCount, n.count);
return expectedCount;
}
private void _checkBst(Node<E> n) {
if (null != n.left) {
assert comparator.compare(n.left.value, n.value) <= 0 : String.format("%s <> %s (%d)",
n.left.value, n.value, comparator.compare(n.left.value, n.value));
_checkBst(n.left);
}
if (null != n.right) {
assert 0 <= comparator.compare(n.right.value, n.value) : String.format("%s <> %s (%d)",
n.right.value, n.value, comparator.compare(n.right.value, n.value));
_checkBst(n.right);
}
}
/////// INTERNAL ///////
private static final class Node<T> {
final @Nullable T value;
int count = 1;
@Nullable Node<T> left = null;
@Nullable Node<T> right = null;
Node(@Nullable T value) {
this.value = value;
}
}
private static <T> Comparable<T> comparable(final T value, final Comparator<? super T> comparator) {
return new Comparable<T>() {
@Override
public int compareTo(T another) {
return comparator.compare(value, another);
}
};
}
// FIXME remove this - (see notes at top)
// FIXME use splay instead, and compare with the root after splay to derive c
static final class FindResult<T> {
public final T value;
/** index of value */
public final int index;
/** the result of q.compare(value) */
public final int c;
FindResult(T value, int index, int c) {
this.value = value;
this.index = index;
this.c = c;
}
}
/** Tests if for successive items <code>x</code> from the iterator,
* <code>q.compareTo(x)</code> is monotonically increasing. */
private static <T> boolean isMonotonicallyIncreasing(Iterator<T> sortedItr, Comparable<? super T> q) {
if (!sortedItr.hasNext()) {
return true;
}
int ps = -1;
while (sortedItr.hasNext()) {
int c = q.compareTo(sortedItr.next());
int s = c < 0 ? -1 : 0 < c ? 1 : 0;
if (s < ps) {
return false;
}
ps = s;
}
return true;
}
}