/*
* 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.
*
* Contributions from 2013-2017 where performed either by US government
* employees, or under US Veterans Health Administration contracts.
*
* US Veterans Health Administration contributions by government employees
* are work of the U.S. Government and are not subject to copyright
* protection in the United States. Portions contributed by government
* employees are USGovWork (17USC ยง105). Not subject to copyright.
*
* Contribution by contractors to the US Veterans Health Administration
* during this period are contractually contributed under the
* Apache License, Version 2.0.
*
* See: https://www.usa.gov/government-works
*
* Contributions prior to 2013:
*
* Copyright (C) International Health Terminology Standards Development Organisation.
* Licensed under the Apache License, Version 2.0.
*
*/
package sh.isaac.api.collections.uuidnidmap;
//~--- JDK imports ------------------------------------------------------------
import java.util.ArrayList;
import java.util.UUID;
//~--- non-JDK imports --------------------------------------------------------
import org.apache.mahout.collections.Arithmetic;
import org.apache.mahout.math.Arrays;
//~--- classes ----------------------------------------------------------------
/**
* The Class UuidArrayList.
*
* @author kec
*/
public class UuidArrayList
extends AbstractUuidList {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
//~--- fields --------------------------------------------------------------
/**
* The array buffer into which the elements of the list are stored. The
* capacity of the list is the length of this array buffer.
*
* @serial
*/
protected long[] elements;
//~--- constructors --------------------------------------------------------
/**
* Constructs an empty list.
*/
public UuidArrayList() {
this(10);
}
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity
* the number of elements the receiver can hold without
* auto-expanding itself by allocating new internal memory.
*/
public UuidArrayList(int initialCapacity) {
this(new long[initialCapacity * 2]);
setSizeRaw(0);
}
/**
* Constructs a list containing the specified elements. The initial size and
* capacity of the list is the length of the array.
*
* <b>WARNING:</b> For efficiency reasons and to keep memory usage low,
* <b>the array is not copied</b>. So if subsequently you modify the
* specified array directly via the [] operator, be sure you know what
* you're doing.
*
* @param elements
* the array to be backed by the the constructed list
*/
public UuidArrayList(long[] elements) {
elements(elements);
}
//~--- methods -------------------------------------------------------------
/**
* Appends the specified element to the end of this list.
*
* @param element
* element to be appended to this list.
*/
@Override
public void add(long[] element) {
// overridden for performance only.
final int msbIndex = this.size * 2;
final int lsbIndex = msbIndex + 1;
if (lsbIndex >= this.elements.length) {
ensureCapacity(lsbIndex + 1);
}
this.elements[msbIndex] = element[0];
this.elements[lsbIndex] = element[1];
this.size++;
}
/**
* Adds the.
*
* @param element the element
*/
public void add(UUID element) {
// overridden for performance only.
final int msbIndex = this.size * 2;
final int lsbIndex = msbIndex + 1;
if (lsbIndex >= this.elements.length) {
ensureCapacity(lsbIndex + 1);
}
this.elements[msbIndex] = element.getMostSignificantBits();
this.elements[lsbIndex] = element.getLeastSignificantBits();
this.size++;
}
/**
* Inserts the specified element before the specified position into the
* receiver. Shifts the element currently at that position (if any) and any
* subsequent elements to the right.
*
* @param index
* index before which the specified element is to be inserted
* (must be in [0,size]).
* @param element
* element to be inserted.
* @exception IndexOutOfBoundsException
* index is out of range (
* {@code index < 0 || index > size()}).
*/
@Override
public void beforeInsert(int index, long[] element) {
// overridden for performance only.
if ((index > this.size) || (index < 0)) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.size);
}
final int elementLength = this.size * 2 + 1;
ensureCapacity(elementLength);
final int indexMsb = index * 2;
final int indexLsb = indexMsb + 1;
System.arraycopy(this.elements, indexLsb, this.elements, indexLsb + 1, this.size - index);
this.elements[indexMsb] = element[0];
this.elements[indexLsb] = element[1];
this.size++;
}
/**
* Searches the receiver for the specified value using the binary search
* algorithm. The receiver must <strong>must</strong> be sorted (as by the
* sort method) prior to making this call. If it is not sorted, the results
* are undefined: in particular, the call may enter an infinite loop. If the
* receiver contains multiple elements equal to the specified object, there
* is no guarantee which instance will be found.
*
* @param key
* the value to be searched for.
* @param from
* the leftmost search position, inclusive.
* @param to
* the rightmost search position, inclusive.
* @return index of the search key, if it is contained in the receiver;
* otherwise, {@code (-(<i>insertion point</i>) - 1)}. The
* <i>insertion point</i> is defined as the the point at which the
* value would be inserted into the receiver: the index of the first
* element greater than the key, or {@code receiver.size()}, if all
* elements in the receiver are less than the specified key. Note
* that this guarantees that the return value will be >= 0 if and
* only if the key is found.
* @see org.ihtsdo.cern.colt.Sorting
* @see java.util.Arrays
*/
@Override
public int binarySearchFromTo(long[] key, int from, int to) {
return UuidSorting.binarySearchFromTo(this.elements, key, from, to);
}
/**
* Returns a deep copy of the receiver.
*
* @return a deep copy of the receiver.
*/
@Override
public Object clone() {
// overridden for performance only.
final UuidArrayList clone = new UuidArrayList(this.elements.clone());
clone.setSizeRaw(this.size);
return clone;
}
/**
* Returns a deep copy of the receiver; uses {@code clone()} and casts
* the result.
*
* @return a deep copy of the receiver.
*/
public UuidArrayList copy() {
return (UuidArrayList) clone();
}
/**
* Returns the elements currently stored, including invalid elements between
* size and capacity, if any.
*
* <b>WARNING:</b> For efficiency reasons and to keep memory usage low,
* <b>the array is not copied</b>. So if subsequently you modify the
* returned array directly via the [] operator, be sure you know what you're
* doing.
*
* @return the elements currently stored.
*/
@Override
public long[] elements() {
return this.elements;
}
/**
* Sets the receiver's elements to be the specified array (not a copy of
* it).
*
* The size and capacity of the list is the length of the array.
* <b>WARNING:</b> For efficiency reasons and to keep memory usage low,
* <b>the array is not copied</b>. So if subsequently you modify the
* specified array directly via the [] operator, be sure you know what
* you're doing.
*
* @param elements
* the new elements to be stored.
* @return the receiver itself.
*/
@Override
public AbstractUuidList elements(long[] elements) {
this.elements = elements;
this.size = elements.length;
return this;
}
/**
* Ensures that the receiver can hold at least the specified number of
* elements without needing to allocate new internal memory. If necessary,
* allocates new internal memory and increases the capacity of the receiver.
*
* @param minCapacity
* the desired minimum capacity.
*/
@Override
public void ensureCapacity(int minCapacity) {
this.elements = Arrays.ensureCapacity(this.elements, minCapacity);
}
/**
* Compares the specified Object with the receiver. Returns true if and only
* if the specified Object is also an ArrayList of the same type, both Lists
* have the same size, and all corresponding pairs of elements in the two
* Lists are identical. In other words, two Lists are defined to be equal if
* they contain the same elements in the same order.
*
* @param otherObj
* the Object to be compared for equality with the receiver.
* @return true if the specified Object is equal to the receiver.
*/
@Override
public boolean equals(Object otherObj) { // delta
// overridden for performance only.
if (otherObj == null) {
return false;
}
if (this == otherObj) {
return true;
}
if (!(otherObj instanceof UuidArrayList)) {
return super.equals(otherObj);
}
final UuidArrayList other = (UuidArrayList) otherObj;
if (size() != other.size()) {
return false;
}
final long[] theElements = elements();
final long[] otherElements = other.elements();
for (int i = size(); --i >= 0; ) {
if (theElements[i] != otherElements[i]) {
return false;
}
}
return true;
}
/**
* Applies a procedure to each element of the receiver, if any. Starts at
* index 0, moving rightwards.
*
* @param procedure
* the procedure to be applied. Stops iteration if the procedure
* returns {@code false}, otherwise continues.
* @return {@code false} if the procedure stopped before all elements where
* iterated over, {@code true} otherwise.
*/
@Override
public boolean forEach(UuidProcedure procedure) {
// overridden for performance only.
final long[] theElements = this.elements;
final int theSize = this.size;
for (int i = 0; i < theSize; i++) {
final int msb = i * 2;
final int lsb = msb + 1;
final long[] uuid = new long[2];
uuid[0] = theElements[msb];
uuid[0] = theElements[lsb];
if (!procedure.apply(uuid)) {
return false;
}
}
return true;
}
/**
* Hash code.
*
* @return the int
*/
@Override
public int hashCode() {
return super.hashCode();
}
/**
* Returns the index of the first occurrence of the specified element.
* Returns {@code -1} if the receiver does not contain this element.
* Searches between {@code from}, inclusive and {@code to},
* inclusive. Tests for identity.
*
* @param element
* element to search for.
* @param from
* the leftmost search position, inclusive.
* @param to
* the rightmost search position, inclusive.
* @return the index of the first occurrence of the element in the receiver;
* returns {@code -1} if the element is not found.
* @exception IndexOutOfBoundsException
* index is out of range (
* {@code size()>0 && (from<0 || from>to || to>=size())}
* ).
*/
public int indexOfFromTo(long element, int from, int to) {
// overridden for performance only.
if (this.size == 0) {
return -1;
}
checkRangeFromTo(from, to, this.size);
final long[] theElements = this.elements;
for (int i = from; i <= to; i++) {
if (element == theElements[i]) {
return i;
} // found
}
return -1; // not found
}
/**
* Returns the index of the last occurrence of the specified element.
* Returns {@code -1} if the receiver does not contain this element.
* Searches beginning at {@code to}, inclusive until {@code from},
* inclusive. Tests for identity.
*
* @param element
* element to search for.
* @param from
* the leftmost search position, inclusive.
* @param to
* the rightmost search position, inclusive.
* @return the index of the last occurrence of the element in the receiver;
* returns {@code -1} if the element is not found.
* @exception IndexOutOfBoundsException
* index is out of range (
* {@code size()>0 && (from<0 || from>to || to>=size())}
* ).
*/
public int lastIndexOfFromTo(long element, int from, int to) {
// overridden for performance only.
if (this.size == 0) {
return -1;
}
checkRangeFromTo(from, to, this.size);
final long[] theElements = this.elements;
for (int i = to; i >= from; i--) {
if (element == theElements[i]) {
return i;
} // found
}
return -1; // not found
}
/**
* Returns a new list of the part of the receiver between {@code from},
* inclusive, and {@code to}, inclusive.
*
* @param from
* the index of the first element (inclusive).
* @param to
* the index of the last element (inclusive).
* @return a new list
* @exception IndexOutOfBoundsException
* index is out of range (
* {@code size()>0 && (from<0 || from>to || to>=size())}
* ).
*/
@Override
public AbstractUuidList partFromTo(int from, int to) {
if (this.size == 0) {
return new UuidArrayList(0);
}
checkRangeFromTo(from, to, this.size);
final long[] part = new long[to - from + 1];
System.arraycopy(this.elements, from, part, 0, to - from + 1);
return new UuidArrayList(part);
}
/**
* Removes from the receiver all elements that are contained in the
* specified list. Tests for identity.
*
* @param other
* the other list.
* @return {@code true} if the receiver changed as a result of the
* call.
*/
@Override
public boolean removeAll(AbstractUuidList other) {
// overridden for performance only.
if (!(other instanceof UuidArrayList)) {
return super.removeAll(other);
}
/*
* There are two possibilities to do the thing a) use other.indexOf(...)
* b) sort other, then use other.binarySearch(...)
*
* Let's try to figure out which one is faster. Let M=size,
* N=other.size, then a) takes O(M*N) steps b) takes O(N*logN + M*logN)
* steps (sorting is O(N*logN) and binarySearch is O(logN))
*
* Hence, if N*logN + M*logN < M*N, we use b) otherwise we use a).
*/
if (other.size() == 0) {
return false;
} // nothing to do
final int limit = other.size() - 1;
int j = 0;
final long[] theElements = this.elements;
final int mySize = size();
final double N = other.size();
final double M = mySize;
if ((N + M) * Arithmetic.log2(N) < M * N) {
// it is faster to sort other before searching in it
final UuidArrayList sortedList = (UuidArrayList) other.clone();
sortedList.quickSort();
for (int i = 0; i < mySize; i++) {
final int msb = i * 2;
final int lsb = msb + 1;
final long[] key = new long[2];
key[0] = theElements[msb];
key[1] = theElements[lsb];
if (sortedList.binarySearchFromTo(key, 0, limit) < 0) {
theElements[2 * j] = theElements[2 * i];
theElements[2 * j + 1] = theElements[2 * i + 1];
j++;
}
}
} else {
// it is faster to search in other without sorting
for (int i = 0; i < mySize; i++) {
final int msb = i * 2;
final int lsb = msb + 1;
final long[] key = new long[2];
key[0] = theElements[msb];
key[1] = theElements[lsb];
if (other.indexOfFromTo(key, 0, limit) < 0) {
theElements[2 * j] = theElements[2 * i];
theElements[2 * j + 1] = theElements[2 * i + 1];
j++;
}
}
}
final boolean modified = (j != mySize);
setSize(j);
return modified;
}
/**
* Replaces a number of elements in the receiver with the same number of
* elements of another list. Replaces elements in the receiver, between
* {@code from} (inclusive) and {@code to} (inclusive), with
* elements of {@code other}, starting from {@code otherFrom}
* (inclusive).
*
* @param from
* the position of the first element to be replaced in the
* receiver
* @param to
* the position of the last element to be replaced in the
* receiver
* @param other
* list holding elements to be copied into the receiver.
* @param otherFrom
* position of first element within other list to be copied.
*/
@Override
public void replaceFromToWithFrom(int from, int to, AbstractUuidList other, int otherFrom) {
// overridden for performance only.
if (!(other instanceof UuidArrayList)) {
// slower
super.replaceFromToWithFrom(from, to, other, otherFrom);
return;
}
final int length = to - from + 1;
if (length > 0) {
checkRangeFromTo(from, to, size());
checkRangeFromTo(otherFrom, otherFrom + length - 1, other.size());
System.arraycopy(((UuidArrayList) other).elements, otherFrom, this.elements, from, length);
}
}
/**
* Retains (keeps) only the elements in the receiver that are contained in
* the specified other list. In other words, removes from the receiver all
* of its elements that are not contained in the specified other list.
*
* @param other
* the other list to test against.
* @return {@code true} if the receiver changed as a result of the
* call.
*/
@Override
public boolean retainAll(AbstractUuidList other) {
// overridden for performance only.
if (!(other instanceof UuidArrayList)) {
return super.retainAll(other);
}
/*
* There are two possibilities to do the thing a) use other.indexOf(...)
* b) sort other, then use other.binarySearch(...)
*
* Let's try to figure out which one is faster. Let M=size,
* N=other.size, then a) takes O(M*N) steps b) takes O(N*logN + M*logN)
* steps (sorting is O(N*logN) and binarySearch is O(logN))
*
* Hence, if N*logN + M*logN < M*N, we use b) otherwise we use a).
*/
final int limit = other.size() - 1;
int j = 0;
final long[] theElements = this.elements;
final int mySize = size();
final double N = other.size();
final double M = mySize;
if ((N + M) * Arithmetic.log2(N) < M * N) {
// it is faster to sort other before searching in it
final UuidArrayList sortedList = (UuidArrayList) other.clone();
sortedList.quickSort();
for (int i = 0; i < mySize; i++) {
final int msb = i * 2;
final int lsb = msb + 1;
final long[] key = new long[2];
key[0] = theElements[msb];
key[1] = theElements[lsb];
if (sortedList.binarySearchFromTo(key, 0, limit) >= 0) {
theElements[2 * j] = theElements[2 * i];
theElements[2 * j + 1] = theElements[2 * i + 1];
j++;
}
}
} else {
// it is faster to search in other without sorting
for (int i = 0; i < mySize; i++) {
final int msb = i * 2;
final int lsb = msb + 1;
final long[] key = new long[2];
key[0] = theElements[msb];
key[1] = theElements[lsb];
if (other.indexOfFromTo(key, 0, limit) >= 0) {
theElements[2 * j] = theElements[2 * i];
theElements[2 * j + 1] = theElements[2 * i + 1];
j++;
}
}
}
final boolean modified = (j != mySize);
setSize(j);
return modified;
}
/**
* Reverses the elements of the receiver. Last becomes first, second last
* becomes second first, and so on.
*/
@Override
public void reverse() {
// overridden for performance only.
final long[] tmp = new long[2];
final int limit = this.size / 2;
int j = this.size - 1;
final long[] theElements = this.elements;
for (int i = 0; i < limit; ) { // swap
tmp[0] = theElements[i * 2];
tmp[1] = theElements[i * 2 + 1];
theElements[i * 2] = theElements[j * 2];
theElements[i * 2 + 1] = theElements[j * 2 + 1];
i++;
theElements[j * 2] = tmp[0];
theElements[j * 2 + 1] = tmp[1];
j--;
}
}
/**
* Sorts the specified range of the receiver into ascending order.
*
* The sorting algorithm is dynamically chosen according to the
* characteristics of the data set. Currently quicksort and countsort are
* considered. Countsort is not always applicable, but if applicable, it
* usually outperforms quicksort by a factor of 3-4.
*
* <p>
* Best case performance: O(N).
* <dt>Worst case performance: O(N^2) (a degenerated quicksort).
* <dt>Best case space requirements: 0 KB.
* <dt>Worst case space requirements: 40 KB.
*
* @param from
* the index of the first element (inclusive) to be sorted.
* @param to
* the index of the last element (inclusive) to be sorted.
* @exception IndexOutOfBoundsException
* index is out of range ({@code size()>0 && (from<0 ||
* from>to || to>=size())}).
*/
@Override
public void sortFromTo(int from, int to) {
if (this.size == 0) {
return;
}
checkRangeFromTo(from, to, this.size);
quickSortFromTo(from, to);
}
/**
* To list.
*
* @return the array list
*/
public ArrayList<UUID> toList() {
final ArrayList<UUID> resultList = new ArrayList<>(this.size);
for (int i = 0; i < this.size; i++) {
resultList.add(new UUID(this.elements[i * 2], this.elements[i * 2 + 1]));
}
return resultList;
}
/**
* Trims the capacity of the receiver to be the receiver's current size.
* Releases any superfluous internal memory. An application can use this
* operation to minimize the storage of the receiver.
*/
@Override
public void trimToSize() {
this.elements = Arrays.trimToCapacity(this.elements, size() * 2 + 1);
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the capacity.
*
* @return the capacity
*/
public int getCapacity() {
return this.elements.length / 2;
}
/**
* Returns the element at the specified position in the receiver.
*
* @param index index of element to return.
* @param nid the nid
* @return the long[]
* @exception IndexOutOfBoundsException index is out of range (index < 0 || index >=
* size()).
*/
public long[] get(int index, int nid) {
// overridden for performance only.
assert index < this.size:
" index out of bounds. index: " + index + " current size: " + this.size + " nid: " + nid;
assert index >= 0:
" index out of bounds (cannot be netagive). index: " + index + " current size: " + this.size + " nid: " +
nid;
return getUuid(index);
}
/**
* Returns the element at the specified position in the receiver;
* <b>WARNING:</b> Does not check preconditions. Provided with invalid
* parameters this method may return invalid elements without throwing any
* exception! <b>You should only use this method when you are absolutely
* sure that the index is within bounds.</b> Precondition (unchecked):
* {@code index >= 0 && index < size()}.
*
* @param index index of element to return.
* @return the quick
*/
@Override
public long[] getQuick(int index) {
return getUuid(index);
}
//~--- set methods ---------------------------------------------------------
/**
* Replaces the element at the specified position in the receiver with the
* specified element; <b>WARNING:</b> Does not check preconditions. Provided
* with invalid parameters this method may access invalid indexes without
* throwing any exception! <b>You should only use this method when you are
* absolutely sure that the index is within bounds.</b> Precondition
* (unchecked): {@code index >= 0 && index < size()}.
*
* @param index
* index of element to replace.
* @param element
* element to be stored at the specified position.
*/
public void setQuick(int index, long element) {
this.elements[index] = element;
}
/**
* Set quick.
*
* @param index the index
* @param element the element
*/
@Override
protected void setQuick(int index, long[] element) {
this.elements[index * 2] = element[0];
this.elements[index * 2 + 1] = element[1];
}
/**
* Replaces the element at the specified position in the receiver with the
* specified element.
*
* @param index
* index of element to replace.
* @param element
* element to be stored at the specified position.
* @exception IndexOutOfBoundsException
* index is out of range (index < 0 || index >=
* size()).
*/
public void set(int index, long element) {
// overridden for performance only.
if ((index >= this.size) || (index < 0)) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.size);
}
this.elements[index] = element;
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the uuid.
*
* @param index the index
* @return the uuid
*/
private long[] getUuid(int index) {
final int msb = index * 2;
final int lsb = msb + 1;
final long[] returnValue = new long[2];
returnValue[0] = this.elements[msb];
returnValue[1] = this.elements[lsb];
return returnValue;
}
}