/*******************************************************************************
* Copyright 2012 Analog Devices, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
package com.analog.lyric.collect;
import java.util.Arrays;
import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.Random;
import org.eclipse.jdt.annotation.Nullable;
/**
* Abstract base class for collections based on a skip list.
* <p>
* This class is intended to be subclassed to implement various types of
* collections based on a skip list. There is no reason to declare this
* type directly in interfaces.
*
* @author Christopher Barber
*
* @param <K> is the type of the key contained in the list that is used to
* determine its order.
*
* @see SkipSet
* @see SkipMap
*/
public abstract class AbstractSkipList<K>
{
/*
* State
*/
/*
* Every node in the list is represented as an array of Objects where
* the first slot contains the key, the slots from minDepthOffset to
* maxDepthOffset contain pointers to the next node in the list at each
* depth. Additional slots after the first may be used to store values.
*
* The current implementation uses a scale factor of 4 at each level
* as described in generateNewNodeDepth method, which should result in
* an average of 1 1/3 next pointers per node. Including the Java per-array
* overhead of 3 words, the average memory is 5 1/3 words per node if the
* nodes contain only a key (6 1/3 if you add a single value). By comparison
* Java's TreeMap.Entry takes up 8 words including the Object overhead of 2.
*
* We could theoretically knock a word per node off by replacing the
* Object[] representation with class for each possible depth with
* exactly that many next pointers, but it is probably not worth the
* trouble.
*
* TODO: we may want to add prev pointers at depth 0 to allow for constant time
* access to the tail of the list, and linear order reverse iteration. Adding
* full multi-level prev pointers would probably overly complicate the logic
* but would allow for some speedup in locating nodes.
*/
/** Comparator used to order elements in the list by key. */
protected final Comparator<? super K> comparator;
/** Head node points to first nodes in list at every depth. Key slot is null. */
protected Object[] head;
/** Offset of next pointer with lowest depth. */
protected final short minDepthOffset;
/** Offset of next pointer with greatest depth currently in the list. */
protected short maxDepthOffset;
/** The current size of the list */
private int size;
/*
* Private methods
*/
private static final ThreadLocal<Object[]> tempPrecursorNode = new ThreadLocal<Object[]>();
final static Object[] allocatePrecursorNode()
{
Object[] precursor = AbstractSkipList.tempPrecursorNode.get();
if (precursor == null)
{
precursor = new Object[20];
}
else
{
AbstractSkipList.tempPrecursorNode.set(null);
}
return precursor;
}
/**
* Computes precursor node for first node in the list.
* @see #makePrecursorNode
*/
private final Object[] makeFirstPrecursorNode()
{
Object[] precursor = allocatePrecursorNode();
Object[] firstNode = this.firstNode();
final int minOffset = this.minDepthOffset;
int maxOffset = minOffset;
if (firstNode != null)
{
maxOffset = Math.min(this.getNodeMaxDepthOffset(firstNode) + 1, this.maxDepthOffset);
}
Arrays.fill(precursor, minOffset, maxOffset + 1, this.head);
return precursor;
}
/**
* Computes an abstract "precursor" node for given {@code key}. This
* returns a node that is not actually in the list and whose next
* pointer slots at each level points at the node that immediately
* precedes the lowest node with key greater than or equal to {@code key}.
* Return node for reuse using {@link #releasePrecursorNode}.
*/
private final Object[] makePrecursorNode(@Nullable K key)
{
Object[] precursor = allocatePrecursorNode();
final Comparator<? super K> c = this.comparator;
Object[] node = this.head;
for (int i = this.maxDepthOffset; i >= this.minDepthOffset; --i)
{
while (true)
{
final @Nullable Object[] nextAtLevel = this.getNextNodeAtDepthOffset(node, i);
if (nextAtLevel == null || c.compare(this.getNodeKey(nextAtLevel), key) >= 0)
{
break;
}
node = nextAtLevel;
}
this.setNextNodeAtDepthOffset(precursor, i, node);
}
return precursor;
}
/**
* Release a node previously returned by {@link #makePrecursorNode}.
* The {@code node} must not be used after calling this function.
* This will null out next pointer slots up to the current value
* of {@link #maxDepthOffset}.
*/
private void releasePrecursorNode(Object[] node)
{
Arrays.fill(node, this.minDepthOffset, this.maxDepthOffset + 1, null);
AbstractSkipList.tempPrecursorNode.set(node);
}
private final void removeNextNode(Object[] precursor)
{
Object[] x = this.getNextNode(precursor);
assert(x != null);
Object[] node = this.getNextNode(x);
assert(node != null);
short maxOffset = this.maxDepthOffset;
for (int i = this.minDepthOffset; i <= maxOffset; ++i)
{
Object[] prev = this.getNextNodeAtDepthOffset(precursor, i);
assert(prev != null);
if (this.getNextNodeAtDepthOffset(prev, i) != node)
{
break;
}
this.setNextNodeAtDepthOffset(prev, i, this.getNextNodeAtDepthOffset(node, i));
}
while (maxOffset > this.minDepthOffset && this.getNextNodeAtDepthOffset(this.head, maxOffset) == null)
{
this.setNextNodeAtDepthOffset(precursor, maxOffset, null);
--maxOffset;
}
if (this.maxDepthOffset != maxOffset)
{
this.maxDepthOffset = maxOffset;
// Shrink head node.
this.head = Arrays.copyOf(this.head, maxOffset + 1);
}
--this.size;
}
/*
* Construction
*/
protected AbstractSkipList(Comparator<? super K> comparator, short minDepth)
{
this.comparator = comparator;
this.minDepthOffset = minDepth;
this.maxDepthOffset = minDepth;
this.size = 0;
this.head = this.makeNode(0, null);
}
/*
* Public methods
*/
/** Removes all elements from the list. */
public void clear()
{
if (this.head.length > this.minDepthOffset + 1)
{
this.head = new Object[this.minDepthOffset + 1];
}
else
{
this.head[this.minDepthOffset] = null;
}
this.size = 0;
this.maxDepthOffset = this.minDepthOffset;
}
public Comparator<? super K> comparator()
{
return this.comparator;
}
/** True if {@link #size()} is zero */
public final boolean isEmpty()
{
return this.size == 0;
}
/** The number of elements currently in the list. */
public final int size() {
return this.size;
}
/*
* Protected methods
*/
/** Finds node with given {@code key}, adding a new one if missing, and returns it. */
protected final Object[] addNode(@Nullable K key)
{
final Object[] precursor = this.makePrecursorNode(key);
final Object[] x = this.getNextNode(precursor);
assert(x != null);
Object[] node = this.getNextNode(x);
if (node == null || comparator.compare(this.getNodeKey(node), key) != 0)
{
short depth = generateNewNodeDepth(size);
node = this.makeNode(depth, key);
final short maxOffset = (short)(depth + this.minDepthOffset);
if (maxOffset > maxDepthOffset)
{
Object[] curHead = this.head;
if (maxOffset >= curHead.length)
{
// Grow the head node to fit.
this.head = Arrays.copyOf(curHead, maxOffset + 1);
// And replace it in precursor node.
for (int i = this.minDepthOffset; i < curHead.length; ++i)
{
if (this.getNextNodeAtDepthOffset(precursor, i) == curHead)
{
this.setNextNodeAtDepthOffset(precursor, i, this.head);
}
}
curHead = this.head;
}
for (int i = maxDepthOffset + 1; i <= maxOffset; i++)
{
this.setNextNodeAtDepthOffset(precursor, i, curHead);
}
maxDepthOffset = maxOffset;
}
for (int i = this.minDepthOffset; i <= maxOffset; i++)
{
Object[] prevNode = this.getNextNodeAtDepthOffset(precursor, i);
assert(prevNode != null);
this.setNextNodeAtDepthOffset(node, i, this.getNextNodeAtDepthOffset(prevNode, i));
this.setNextNodeAtDepthOffset(prevNode, i, node);
}
++this.size;
}
this.releasePrecursorNode(precursor);
return node;
}
protected final boolean containsNode(K key)
{
Object[] node = this.findCeilingNode(key);
return node != null ? key.equals(this.getNodeKey(node)) : false;
}
/**
* Returns the node containing the least element in the set with value greater than or
* equal to {@code value}. Returns null if there is no such value.
*/
protected final @Nullable Object[] findCeilingNode(K key)
{
return this.getNextNode(this.findLowerNode(key));
}
protected final @Nullable Object[] findFloorNode(K key)
{
Object[] node = this.findLowerNode(key);
while (true)
{
node = this.getNextNode(node);
if (node == null || this.comparator.compare(key, this.getNodeKey(node)) >= 0)
{
break;
}
}
return node;
}
protected final @Nullable Object[] findHigherNode(K key)
{
Object[] node = this.findCeilingNode(key);
while (node != null && this.comparator.compare(key, this.getNodeKey(node)) == 0)
{
node = this.getNextNode(node);
}
return node;
}
/**
* Returns the node containing the greatest value strictly less than {@code value}.
* Returns the {@link #head} node if there is no such value in the set.
*/
protected final Object[] findLowerNode(K key)
{
Object[] precursor = this.makePrecursorNode(key);
Object[] node = this.getNextNode(precursor);
this.releasePrecursorNode(precursor);
assert(node != null);
return node;
}
protected final @Nullable Object[] firstNode()
{
if (this.isEmpty())
{
throw new NoSuchElementException();
}
return this.getNextNode(head);
}
static private Random random = new Random();
/**
* Returns a new randomly generated depth to be used for a new node.
* <p>
* This version produces a logarithmically distributed random value such that
* the number of nodes at level n is expected to be 4 times the number at level
* n + 1 and the expected depth of the resulting list is log4(size).
* <p>
* The factor 4 was chosen to minimize the average number of compares required
* to locate an element in the list. Assuming a logarithmic distribution with base b,
* it should require an average of between 1 and b + 1 compares at each level. So
* the expected number of compares is:
* <p>
* (b + 2)/2 * log<sub>b</sub>(size)
* <p>
* so we want to find the minimum value of b > 1 of
* <p>
* (b + 2) / 2ln(b)
* <p>
* which is ~4.3. Since this is close to 4, and log4 can be calculated efficiently
* for integers. That is what we use here.
*/
protected static short generateNewNodeDepth(int size)
{
// maxDepth ~ log4(curSize)
final int maxDepth = (32 - Integer.numberOfLeadingZeros(size)) / 2;
short depth = 0;
if (maxDepth != 0)
{
final int r = random.nextInt((2 << maxDepth) - 1);
final int inverseDepth = (32 - Integer.numberOfLeadingZeros(r)) / 2;
depth = (short)(maxDepth - inverseDepth);
}
return depth;
}
/** Returns the next node in the list after {@code node}. */
protected final @Nullable Object[] getNextNode(Object[] node)
{
return (Object[])node[this.minDepthOffset];
}
/** Returns the next node in the list at the given depth offset. */
protected final @Nullable Object[] getNextNodeAtDepthOffset(Object[] node, int depthOffset)
{
return (Object[])node[depthOffset];
}
protected final @Nullable Object[] getNode(K key)
{
Object[]node = this.findCeilingNode(key);
return node != null && key.equals(this.getNodeKey(node)) ? node : null;
}
/** Gets the key from the {@code node} */
protected final K getNodeKey(Object[] node)
{
@SuppressWarnings("unchecked")
K key = (K)node[0];
return key;
}
/** Returns the offset of the next pointer with the greatest depth in {@code node} */
protected final short getNodeMaxDepthOffset(Object[] node)
{
return (short)(node.length - 1);
}
protected final @Nullable Object[] lastNode()
{
Object[] node = null;
if (this.size > 0)
{
node = this.head;
for (int level = this.maxDepthOffset; level >= this.minDepthOffset; --level)
{
while (true)
{
@Nullable Object[] next = this.getNextNodeAtDepthOffset(node, level);
if (next != null)
{
node = next;
}
else
{
break;
}
}
}
}
return node;
}
protected final Object[] makeNode(int maxDepth, @Nullable K key)
{
Object[] node = new Object[maxDepth + this.minDepthOffset + 1];
node[0] = key;
return node;
}
protected final @Nullable Object[] pollFirstNode()
{
Object[] firstNode = this.getNextNode(this.head);
if (firstNode != null)
{
Object[] precursor = this.makeFirstPrecursorNode();
this.removeNextNode(precursor);
this.releasePrecursorNode(precursor);
}
return firstNode;
}
protected final @Nullable Object[] pollLastNode()
{
// TODO: implement pollLastNode more efficiently
Object[] lastNode = this.lastNode();
if (lastNode != null)
{
this.removeNode(this.getNodeKey(lastNode));
}
return lastNode;
}
/**
* Remove and return node with given {@code key}. Returns null if {@code key} is null or
* no such key in list.
*/
protected final @Nullable Object[] removeNode(@Nullable K key)
{
@Nullable Object[] node = null;
if (key != null)
{
final Object[] precursor = this.makePrecursorNode(key);
final Object[] x = this.getNextNode(precursor);
assert(x != null);
node = this.getNextNode(x);
if (node != null)
{
if (comparator.compare(this.getNodeKey(node), key) == 0)
{
this.removeNextNode(precursor);
}
else
{
node = null;
}
}
this.releasePrecursorNode(precursor);
}
return node;
}
/**
* Sets the next node in the list at the given depth offset. The {@code nextNode}
* must either be null or have {@link #getNodeMaxDepthOffset} no less than
* {@code depthOffset}.
*/
protected final void setNextNodeAtDepthOffset(Object[] node, int depthOffset, @Nullable Object[] nextNode)
{
assert(nextNode == null || nextNode.length > depthOffset);
node[depthOffset] = nextNode;
}
/*
* Iterators
*/
/**
* A reusable iterator for fast iteration over keys of a {@link AbstractSkipList}.
* <p>
* Iterating over the entire collection costs O(n) in the size of the collection.
*/
public static class KeyIterator<K> implements java.util.Iterator<K>
{
/*
* State
*/
/** The underlying set to be iterated over. This may be null. */
protected @Nullable AbstractSkipList<K> list;
/** The next node to be returned by {@link #next}. Null when there are no more nodes. */
private @Nullable Object[] nextNode;
/** The last key returned by {@link #next}. Could be null. */
private @Nullable K lastKey;
/*
* Construction/initialization methods
*/
/**
* Constructs iterator over given {@code list}, which may be null.
*/
public KeyIterator(@Nullable AbstractSkipList<K> list)
{
this.reset(list);
}
/**
* Resets iterator back to beginning of set.
*/
public void reset()
{
final @Nullable AbstractSkipList<K> list2 = this.list;
this.nextNode = list2 == null ? null : list2.getNextNode(list2.head);
this.lastKey = null;
}
/**
* Resets iterator to beginning of {@code newList}, which may be null.
*/
public void reset(@Nullable AbstractSkipList<K> newList)
{
this.list = newList;
this.reset();
}
/*
* java.util.Iterator methods
*/
/**
* Returns true if {@link #next} method will return a non-null value.
*/
@Override
public boolean hasNext()
{
return this.nextNode != null;
}
/**
* Returns the next element in the iteration or null if at the end of the list.
* It is not necessary to invoke {@link #hasNext} before calling this method.
*/
@Override
public @Nullable K next()
{
final AbstractSkipList<K> list2 = list;
K key = null;
if (list2 != null)
{
Object[] n = this.nextNode;
if (n != null)
{
key = list2.getNodeKey(n);
this.nextNode = list2.getNextNode(n);
}
this.lastKey = key;
}
return key;
}
/**
* Removes the entry that was most recently returned by the {@link #next} method.
* Costs O(log n).
*/
@Override
public void remove()
{
final @Nullable AbstractSkipList<K> list2 = this.list;
if (list2 == null || this.lastKey == null)
{
throw new IllegalStateException();
}
list2.removeNode(this.lastKey);
this.lastKey = null;
}
}
}