package statalign.base;
/**
* Dynamically growing circular array with extremely efficient deque and hash operations.
*
* Can be used as a dynamically growing hash (with semi-contiguous key range for
* space-efficiency), deque (queue or stack), array, perhaps in turns.
* Offers amortised O(1) time for each implemented operation.
* Does not shrink.
*
* @author novadam
*
* @param <E> element type
*/
public class CircularArray<E> {
/** Minimum capacity of the array, must be a power of two */
private final static int MIN_CAPACITY = 4;
/** lowest key of elements in array */
int startKey;
/** highest key of elements in array plus 1 */
int endKey;
/** data array, capacity of the <tt>CircularArray</tt> is <tt>data.length</tt> */
E[] data;
/**
* Constructs an empty <tt>CircularArray</tt> with an initial capacity of <i>MIN_CAPACITY</i>.
*/
@SuppressWarnings("unchecked")
public CircularArray() {
data = (E[]) new Object[MIN_CAPACITY];
}
/**
* Constructs an empty <tt>CircularArray</tt> with a given minimal initial capacity
* but at least <i>MIN_CAPACITY</i>.
*
* @param initialCapacity minimum capacity of the new array
*/
@SuppressWarnings("unchecked")
public CircularArray(int initialCapacity) {
if(initialCapacity > MIN_CAPACITY)
initialCapacity = findCapacity(initialCapacity-1);
else
initialCapacity = MIN_CAPACITY;
data = (E[]) new Object[initialCapacity];
}
/**
* Difference between the highest and lowest key of elements in the array plus 1.
* This is the length of the data area which is considered nonempty.
* If array is used solely as a deque (or at least keys are contiguous), this equals the
* number of elements in the array.
*
* @return length as defined above
*/
public int length() {
return endKey-startKey;
}
/**
* Puts a new (key,element) pair into the <tt>CircularArray</tt>, growing the array if
* the new key range doesn't fit into the array.
*
* This operation might destroy the property of contiguous keys.
*
* @param key key of the element
* @param element the element to store at the given key
*/
public void put(int key, E element) {
int sizem1 = data.length-1;
final int sk = startKey;
final int ek = endKey;
if(sk == ek) { // empty array
endKey = (startKey = key) + 1;
} else if(key < sk) { // insertion before first key, length increases
final int newSizem1 = ek-key-1;
if(newSizem1 > sizem1) { // not enough space, must grow array
sizem1 = grow(newSizem1)-1;
}
startKey = key;
} else if(key >= ek) { // insertion after last key, length increases
final int newSizem1 = key-sk;
if(newSizem1 > sizem1) { // not enough space, must grow array
sizem1 = grow(newSizem1)-1; // no need to add 1, see grow
}
endKey = key + 1;
}
data[key & sizem1] = element;
}
/**
* Retrieves the element at the given key in the array or <tt>null</tt> if there is no element
* with that key.
*
* @param key the given key
* @return element at the key or <tt>null</tt>
*/
public E get(int key) {
if(key < startKey || key >= endKey) // out of stored key range
return null;
return data[key & (data.length-1)];
}
/**
* Adds new element at the end of the array (key <tt>endKey</tt> is assigned), growing the
* array if full, then <tt>endKey</tt> is increased.
*
* If key range is contiguous before the call, this operation will maintain the property.
*
* @param elem the element to be added
*/
public void push(E elem) {
int size = data.length;
if(endKey - startKey == size)
grow2(size <<= 1);
data[endKey++ & (size - 1)] = elem;
}
/**
* Retrieves the last element in the array (element with key <tt>endKey-1</tt>) and
* decreases <tt>endKey</tt>.
*
* If key range is contiguous, a <tt>null</tt> value can only be returned if array is empty or
* if <tt>null</tt> values have been added to the array explicitly. Otherwise, <tt>null</tt>
* may be returned for keys with an unspecified value.
*
* @return the last element in the array or <tt>null</tt> if empty
*/
public E pop() {
if(startKey == endKey) // array empty
return null;
final int index = --endKey & (data.length - 1);
final E result = data[index];
data[index] = null; // should we care ?? (only has an effect if array and deque operations used in turns)
return result;
}
/**
* Adds new element at the beginning of the array (key <tt>startKey-1</tt> is assigned), growing
* the array if full, then <tt>startKey</tt> is decreased.
*
* If key range is contiguous before the call, this operation will maintain the property.
*
* @param elem the element to be added
*/
public void unshift(E elem) {
int size = data.length;
if(endKey - startKey == size)
grow2(size <<= 1);
data[--startKey & (size - 1)] = elem;
}
/**
* Retrieves the first element in the array (element with key <tt>startKey</tt>) and
* increases <tt>startKey</tt>.
*
* If key range is contiguous, a <tt>null</tt> value can only be returned if array is empty or
* if <tt>null</tt> values have been added to the array explicitly. Otherwise, <tt>null</tt>
* may be returned for keys with an unspecified value.
* @return the first element in the array or <tt>null</tt> if empty
*/
public E shift() {
if(startKey == endKey)
return null;
final int index = startKey++ & (data.length - 1);
final E result = data[index];
data[index] = null;
return result;
}
/**
* Copies contents of this CircularArray to a regular array. It must be at least of
* length() size.
*
* @return array
*/
public E[] toArray(E[] newData) {
E[] oldData = data;
int size = oldData.length;
int sk = startKey;
int n = endKey-sk;
sk &= size-1;
copy(oldData, newData, sk, 0, size, n);
return newData;
}
/**
* Finds minimal capacity <tt>2^n >= size+1</tt>.
*
* @param size lower bound for capacity
* @return target capacity that is a power of 2 and above <tt>size</tt>
*/
private int findCapacity(int size) {
// Find the best power of two to hold elements.
// Code borrowed (= stolen) from java.util.ArrayDeque
size |= (size >>> 1);
size |= (size >>> 2);
size |= (size >>> 4);
size |= (size >>> 8);
size |= (size >>> 16);
size++;
if (size < 0) // Too many elements, must back off
size >>>= 1; // Good luck allocating 2^30 elements
return size;
}
/**
* Grows array. New size will be <tt>2^n >= size+1</tt>.
*
* @param size lower bound for new size, must be above current size (not checked!)
* @return new size
*/
private int grow(int size) {
final int newSize = findCapacity(size);
grow2(newSize);
return newSize;
}
/**
* Grows array.
*
* @param size new size, must be a power of two
*/
@SuppressWarnings("unchecked")
private void grow2(int newSize) {
E[] newData = (E []) new Object[newSize];
E[] oldData = data;
int size = oldData.length;
int sk = startKey;
int n = endKey-sk;
int nsk = sk & (newSize-1);
sk &= size-1;
int rem1 = newSize-nsk;
int rem2 = n-rem1;
if(rem2 > 0) {
System.arraycopy(oldData, sk, newData, nsk, rem1);
System.arraycopy(oldData, (sk+rem1) & (size-1), newData, 0, rem2);
} else {
copy(oldData, newData, sk, nsk, size, n);
}
data = newData;
}
private void copy(E[] oldData, E[] newData, int sk, int nsk, int size, int n) {
int rem1 = size-sk;
int rem2 = n-rem1;
if(rem2 > 0) {
System.arraycopy(oldData, sk, newData, nsk, rem1);
System.arraycopy(oldData, 0, newData, nsk+rem1, rem2);
} else {
System.arraycopy(oldData, sk, newData, nsk, n);
}
}
}