![JAR search and dependency download from the Maven repository](/logo.png)
com.xdev.jadoth.collections.VarList Maven / Gradle / Ivy
/*
* XDEV Application Framework - XDEV Application Framework
* Copyright © 2003 XDEV Software (https://xdev.software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
/**
*
*/
package com.xdev.jadoth.collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.RandomAccess;
import com.xdev.jadoth.util.VarChar;
/**
* Variable list implementation that is based on the LinkedList concept and extended by indexing and precaching of
* entries to gain fast {@link RandomAccess} and increasing behaviour.
*
* Settings for indexing and caching are highly customizable and quickly changeable during the lifetime of a
* {@link VarList} instance to always achieve high performance in every situation.
*
* The {@link VarList} implementation outperforms {@link LinkedList} in almost every situation.
*
* Comparison to {@link GrowList}:
*
* - If enough initial capacity is reserved and no add~(index) or remove~() operations are needed,
* {@link GrowList} is significantly faster than {@link VarList}.
* - {@link VarList} needs about 3 times as much memory as {@link GrowList} or more depending on the chosen
* index and cache size
* - If heavy inserting/removing of elements is needed or if the type of use is unknown,
* {@link VarList} is a good general purpose list implementation.
*
*
* @author Thomas Muenz
*
*/
public final class VarList implements VarChar.Appendable //XList
{
///////////////////////////////////////////////////////////////////////////
// constants //
/////////////////////
private static final int DEFAULT_INDEX_SIZE = 32;
private static final int DEFAULT_INDEX_SPAN_EXPONENT = 2; //index span: every index entry stands for 4 entries (2^n)
private static final int DEFAULT_INDEX_GROWTH_INDICATOR_EXPONENT = 5; //indexSize = one 32th of list size (2^5 = 32)
private static final int DEFAULT_INDEX_MAX_SPAN_EXPONENT = 10; //max index span = 2^30 = 1 Bil (=~ turn off index)
private static final int DEFAULT_CACHE_CAPACITY = 32;
private static final int DEFAULT_CACHE_SIZE = 32;
public static final class Configuration
{
private Configuration(){}
///////////////////////////////////////////////////////////////////////////
// static fields //
//////////////////
/**
* This threshold will probably be hardware dependant
*/
private static int DEFAULT_INLINE_SORT_THRESHOLD = 10000;
private static char DEFAULT_LIST_SEPERATOR_CHAR = ',';
private static String DEFAULT_LIST_SEPERATOR_STRING = ", ";
///////////////////////////////////////////////////////////////////////////
// getters //
/////////////////////
public static int getDefaultInlineSortThreshold()
{
return DEFAULT_INLINE_SORT_THRESHOLD;
}
public static String getDefaultListSeperatorString()
{
return DEFAULT_LIST_SEPERATOR_STRING;
}
public static char getDefaultListSeperatorChar()
{
return DEFAULT_LIST_SEPERATOR_CHAR;
}
///////////////////////////////////////////////////////////////////////////
// setters //
/////////////////////
public static void setInlineSortThreshold(final int threshold)
{
DEFAULT_INLINE_SORT_THRESHOLD = threshold;
}
public static void setDefaultListSeperatorString(final String defaultListSeperatorString)
{
DEFAULT_LIST_SEPERATOR_STRING = defaultListSeperatorString;
}
public static void setDefaultListSeperatorChar(final char defaultListSeperatorChar)
{
DEFAULT_LIST_SEPERATOR_CHAR = defaultListSeperatorChar;
}
}
///////////////////////////////////////////////////////////////////////////
// instance fields //
////////////////////
// list core fields
private final Entry head = new Entry();
private int size;
// index fields
private Entry[] entryIndex;
private boolean trivialIndex;
private int indexSize = 0;
private int indexSpanExponent;
private int indexSpanModulo;
private int currentMaxListSize;
private int indexGrowthIndicator = DEFAULT_INDEX_GROWTH_INDICATOR_EXPONENT;
private int maxIndexSpanExponent = DEFAULT_INDEX_MAX_SPAN_EXPONENT;
private int nextGetIndex = -30091981;
private Entry nextGetEntry = null;
// cache fields
private Entry[] cache;
private int cacheSize;
// string stuff
private String listSeperatorString = Configuration.getDefaultListSeperatorString();
private char listSeperatorChar = Configuration.getDefaultListSeperatorChar();
private int inlineSortThreshold = Configuration.getDefaultInlineSortThreshold();
///////////////////////////////////////////////////////////////////////////
// constructors //
/////////////////////
public VarList()
{
super();
this.initializeCache(DEFAULT_CACHE_CAPACITY, DEFAULT_CACHE_SIZE);
this.initializeIndex(DEFAULT_INDEX_SIZE, DEFAULT_INDEX_SPAN_EXPONENT);
}
public VarList(final int initialCacheSize)
{
super();
this.initializeCache(initialCacheSize>0 ?initialCacheSize :0, initialCacheSize>0 ?initialCacheSize :0);
this.initializeIndex(DEFAULT_INDEX_SIZE, DEFAULT_INDEX_SPAN_EXPONENT);
}
public VarList(
final int initialCacheSize,
final int indexSize,
final int indexSpanExponent
)
{
super();
this.initializeCache(initialCacheSize>0 ?initialCacheSize :0, initialCacheSize>0 ?initialCacheSize :0);
this.initializeIndex(indexSize, indexSpanExponent);
}
{
this.initializeHead();
}
private void initializeHead()
{
this.head.next = this.head.prev = this.head;
}
///////////////////////////////////////////////////////////////////////////
// getters //
/////////////////////
/**
* @return the listSeperatorString
*/
public String getListSeperatorString()
{
return this.listSeperatorString;
}
/**
* @return the listSeperatorChar
*/
public char getListSeperatorChar()
{
return this.listSeperatorChar;
}
///////////////////////////////////////////////////////////////////////////
// setters //
/////////////////////
/**
* @param listSeperatorString the listSeperatorString to set
*/
public VarList setListSeperatorString(final String listSeperatorString)
{
this.listSeperatorString = listSeperatorString;
return this;
}
/**
* @param listSeperatorChar the listSeperatorChar to set
*/
public VarList setListSeperatorChar(final char listSeperatorChar)
{
this.listSeperatorChar = listSeperatorChar;
return this;
}
///////////////////////////////////////////////////////////////////////////
// declared methods //
/////////////////////
private void initializeIndex(int indexSize, int indexSpanExponent)
{
if(indexSpanExponent > this.maxIndexSpanExponent){
indexSpanExponent = this.maxIndexSpanExponent;
}
if(indexSize < 1){
indexSize = 1;
}
this.entryIndex = new Entry[indexSize];
this.indexSpanExponent = indexSpanExponent;
this.indexSpanModulo = (1<null, by the list seperator char ({@link #setListSeperatorChar(char)},
* {@link #getListSeperatorChar()}).
* Note that using the single char as seperator is faster than using a {@link String}.
*
* @param vc the StringBuilder to append to.
* @throws NullPointerException if the passed {@link VarChar} object is null.
*/
@Override
public VarChar appendTo(final VarChar vc) throws NullPointerException
{
if(this.size == 0) return vc;
Entry e = this.entryIndex[0];
vc.append(e.value);
Object value;
if(this.listSeperatorString != null){
final char[] sepp = this.listSeperatorString.toCharArray();
while((e = e.next) != null){
value = e.value;
vc.append(sepp).append(value == this ?super.toString() :value);
}
}
else {
final char sepp = this.listSeperatorChar;
while((e = e.next) != null){
value = e.value;
vc.append(sepp).append(value == this ?super.toString() :value);
}
}
return vc;
}
/**
* Note that an invalid index value will cause a runtime exception either of the type
* {@link ArrayIndexOutOfBoundsException} when accessing the index or an {@link NullPointerException}
* when trying to scrolling to an entry that doesn't exist.
* In either case, the information provided by the exception will not be of much use. Still, the
* calling context knows the value of the index it has passed to this method, so no information is lost.
*
* @param index
* @return
*/
@SuppressWarnings("unchecked")
public E get(final int index)
{
if(this.nextGetIndex == index){
//sequential index access optimisation
final Entry e = this.nextGetEntry;
if(e.next != null){
this.nextGetIndex++;
this.nextGetEntry = e.next;
}
else {
this.nextGetIndex = 0;
this.nextGetEntry = this.head.next;
}
return (E)e.value;
}
else if(index >= this.size){
// unnecessary but java.util.List-conform index checking
throw new IndexOutOfBoundsException(Integer.toString(index));
}
// index check is not required here as any invalid index will cause an exception later without harm
Entry hopEntry = this.entryIndex[index>>this.indexSpanExponent];
for(int hops = index & this.indexSpanModulo; hops --> 0;){
hopEntry = hopEntry.next;
}
if(hopEntry.next != null){
this.nextGetIndex = index+1;
this.nextGetEntry = hopEntry.next;
}
return (E)hopEntry.value;
}
/**
* In principle the same as {@link #get(int)}, only difference is that no sequential index access optimisation is
* done and invalid index values cause different types of exceptions instead of the standard
* {@link IndexOutOfBoundsException}.
* Still, it is ensured that an invalid index value will cause an exception.
*
* Prefer this method over {@link #get(int)} for unsequential (i.e. randomized) accesses.
*
* @param index
* @return the value
* @throws ArrayIndexOutOfBoundsException if index is negative or >= size()
* @throws NullPointerException if index is >= size()
*/
@SuppressWarnings("unchecked")
public E peek(final int index)
{
// index check is not required here as any invalid index will cause an exception later without harm
Entry hopEntry = this.entryIndex[index>>this.indexSpanExponent];
for(int hops = index & this.indexSpanModulo; hops --> 0;){
hopEntry = hopEntry.next;
}
return (E)hopEntry.value;
}
private Entry newEntry()
{
if(this.cacheSize == 0){
return new Entry();
}
final Entry e = this.cache[--this.cacheSize];
this.cache[this.cacheSize] = null;
return e;
}
@SuppressWarnings("unchecked")
public E prepend(final E element)
{
// update list size and index size if needed
if(this.size == this.currentMaxListSize){
this.enlargeIndex();
}
this.size++; //increment size only if index enlarging did not throw an exception
// create entry and join in on the head of the list
final Entry entry = this.newEntry();
entry.value = element;
entry.prev = this.head;
this.head.next = entry;
// update index for first entry
final Entry[] entryIndex = this.entryIndex;
entryIndex[0] = entry; //harcoded array index access yields better performance
// store index size for special case check and later use for index update
final int indexSize = this.indexSize;
// special case: if this was the first element, set index size to one and return null
if(indexSize == 0){
this.indexSize = 1;
return null;
}
// join in with so far first element in normal case
entry.next = this.head.next;
entry.next.prev = entry;
// no trivial index case handling needed here because: if index is trivial but not size 0, it's already size 1.
// update all existing indexed entries AFTER the one responsible for the new entry (thus pre-increment)
int indexIndex = 0;
while(++indexIndex < indexSize){
entryIndex[indexIndex] = entryIndex[indexIndex].prev; //one read access, one write acces
}
// if the new size exceeds the index span by exactely one, a new index entry is required
if((this.size & this.indexSpanModulo) == 1){
// the enlargement check above ensured that in this case, the index now has spare room (pretty cool)
entryIndex[indexIndex] = this.head.prev; //indexIndex is always indexSize+1 at this point
this.indexSize++; //faster?: "this.indexSize = indexIndex"
}
return (E)entry.next.value;
}
public void add(final E element)
{
// update list size and index size if necessary
final int elementIndex = this.size;
if(elementIndex == this.currentMaxListSize){
this.enlargeIndex();
}
this.size++; //increment size only if index enlarging did not throw an exception
// create entry and join in on the head of the list
final Entry entry = this.newEntry();
entry.value = element;
entry.prev = this.head.prev;
this.head.prev = entry;
entry.prev.next = entry;
// entry.next = head; //never reference head in forward direction
// keep index updating simple and fast for the trivial case
if(this.trivialIndex){
if(this.entryIndex[0] == null){
this.entryIndex[0] = entry;
this.indexSize = 1;
}
return;
}
// update entryIndex if necessary (doesn't happen very often, so spare the temporary variable)
if(this.entryIndex[elementIndex>>this.indexSpanExponent] == null){
this.entryIndex[elementIndex>>this.indexSpanExponent] = entry;
this.indexSize++;
}
}
public void add(final int index, final E element)
{
if(index >= this.size){
if(index > this.size){ //make unnecessary List-conform bounds check in here only
throw new IndexOutOfBoundsException();
}
this.add(element); //to handle the "append" special case (index == size) correctly
return;
}
// update list size and index size if needed
if(this.size == this.currentMaxListSize){
this.enlargeIndex();
}
// locate current entry at that index
int indexIndex = index>>this.indexSpanExponent;
Entry hopEntry = this.entryIndex[indexIndex];
for(int hops = index & this.indexSpanModulo; hops --> 0;){
hopEntry = hopEntry.next;
}
//join in new entry
final int cacheSize = this.cacheSize;
final Entry entry = cacheSize == 0 ? new Entry() :this.cache[--this.cacheSize];
entry.prev = hopEntry.prev; //adding to this.size+1 will throw an NPE here (intentionally)
entry.next = hopEntry;
entry.prev.next = entry;
hopEntry.prev = entry;
// setting element, new size and removing cached entry not until valid insert is ensured!
entry.value = element;
this.size++;
if(cacheSize > 0){
this.cache[cacheSize] = null;
}
// set new entry as indexed entry if necessary
final Entry[] entryIndex = this.entryIndex; //must be assigned AFTER enlargeIndex()!
if((index & this.indexSpanModulo) == 0){
entryIndex[indexIndex] = entry;
}
// update sequential access index if necessary
if(index <= this.nextGetIndex){
this.nextGetIndex++;
}
// update all existing indexed entries AFTER the one responsible for the new entry (thus pre-increment)
//OLD version without indexSize helper variable
// for(final int entryIndexLength = entryIndex.length; ++indexIndex < entryIndexLength;){
// if((hopEntry = entryIndex[indexIndex]) == null){
// // check if a new index entry is required: if size exceed span by exactely 1, index that last entry
// if((this.size & this.indexSpanModulo) == 1){
// entryIndex[indexIndex] = this.head.prev;
// }
// break; //as this was the last index entry, there's no need to iterate to the end of the index array
// }
// entryIndex[indexIndex] = hopEntry.prev;
// }
// update all existing indexed entries AFTER the one responsible for the new entry (thus pre-increment)
final int indexSize = this.indexSize;
while(++indexIndex < indexSize){
entryIndex[indexIndex] = entryIndex[indexIndex].prev; //one read access, one write acces
}
// if the new size exceeds the index span by exactely one, a new index entry is required
if((this.size & this.indexSpanModulo) == 1){
// the enlargement check above ensured that in this case, the index now has spare room (pretty cool)
entryIndex[indexIndex] = this.head.prev; //indexIndex is always indexSize+1 at this point
this.indexSize++;
}
}
@SuppressWarnings("unchecked")
public E set(final int index, final E element) throws IndexOutOfBoundsException
{
if(index >= this.size){ //unnecessary List-conform bounds check
throw new IndexOutOfBoundsException(Integer.toString(index));
}
// index check is not required here as any invalid index will cause an exception later without harm
Entry hopEntry = this.entryIndex[index>>this.indexSpanExponent];
for(int hops = index & this.indexSpanModulo; hops --> 0;){
hopEntry = hopEntry.next;
}
final E oldValue = (E)hopEntry.value;
hopEntry.value = element;
return oldValue;
}
private void enlargeIndex()
{
final Entry[] entryIndex = this.entryIndex;
final int indexLength = entryIndex.length;
final int idxExp = this.indexSpanExponent;
if(1<= indexLength || idxExp == this.maxIndexSpanExponent){
// case 1: grow index size (by factor of 2)
this.entryIndex = new Entry[indexLength<<1]; //provoke Exception if indexLength is 1<<30 (=2^31)
System.arraycopy(entryIndex, 0, this.entryIndex, 0, indexLength);
if(this.trivialIndex){
this.trivialIndex = false;
}
}
else {
// case 2: grow spanExponent (by factor of 2)
this.indexSpanExponent++; // exponent can never grow beyond maxIndexSpanExponent in here
this.indexSpanModulo = (this.indexSpanModulo<<1) + 1;
int i = 1; //leave out first index entry as it always points to the very first entry
// half the index entry count but double their segment count (by assigning the doubled so far indexed entry)
for(final int halfIndexIndex = indexLength>>1; i < halfIndexIndex; i++){
entryIndex[i] = entryIndex[i<<1]; //nice: null references don't cause any harm here
}
this.indexSize = i;
while(i < indexLength){ // clear the rest of the index array
entryIndex[i++] = null;
}
}
this.currentMaxListSize <<= 1; //either case has increased max size by a factor of 2
}
/**
* Required to rebuild the index after operations where updating the index on each change makes no sense,
* i.e. sorting or adding, inserting, removing of large numbers of data.
*
*/
private void rebuildIndex()
{
// preliminary special inlined simplified version of enlargeIndex()
final int size = this.size;
int indexLength = this.entryIndex.length;
while(size > this.currentMaxListSize){
final int idxExp = this.indexSpanExponent;
if(1<= indexLength || idxExp == this.maxIndexSpanExponent){
// case 1: grow index size (by factor of 2)
if((indexLength <<= 1) > size){ //only allocate new index array if new length is long enough
this.entryIndex = new Entry[indexLength<<1]; //provoke Exception if indexLength is 1<<30 (=2^31)
// no index entry copiing needed as it will be rebuilt anyway
if(this.trivialIndex){
this.trivialIndex = false;
}
}
}
else {
// case 2: grow spanExponent (by factor of 2)
this.indexSpanExponent++; // exponent can never grow beyond maxIndexSpanExponent in here
this.indexSpanModulo = (this.indexSpanModulo<<1) + 1;
}
this.currentMaxListSize <<= 1; //either case has increased max size by a factor of 2
}
// actual index rebuild
final Entry[] entryIndex = this.entryIndex;
final int m = this.indexSpanModulo;
int j = 0;
Entry e = this.head;
for(int i = 0; (e = e.next) != null; i++){
if((i & m) == 0){ //each entry whose i(ndex) fits exactely to the index span is an indexed entry
entryIndex[j++] = e;
}
}
this.indexSize = j;
for(final int indexSize = this.entryIndex.length; j < indexSize; j++){
entryIndex[j] = null; //the rest of the index buckets are free
}
}
public void cacheEnsureSize(final int minimalCacheSize)
{
}
public void cacheSetSize(final int minimalCacheSize)
{
}
public void cacheClear()
{
final Entry[] cache = this.cache;
for(int i = cache.length; i --> 0;){
cache[i] = null;
}
this.cacheSize = 0;
}
public void cacheTruncate()
{
this.cache = new Entry[DEFAULT_CACHE_CAPACITY];
this.cacheSize = 0;
}
public int size()
{
return this.size;
}
private void releaseIntermediateEntry(final Entry entry)
{
// disjoin from list
entry.prev.next = entry.next;
entry.next.prev = entry.prev; //intermediate entries always have a next entry (as opposed to the last entry)
if(this.cacheSize < this.cache.length){
//return entry back to the pool
this.cache[this.cacheSize++] = entry;
//reset the entry and enable GC to collect the stuff entry references so far
entry.value = null;
entry.next = null;
entry.prev = null;
}
//otherwise just let the nwo unrefernced entry lay around and wait for GC to kill it
}
@SuppressWarnings("unchecked")
public E removeFirst()
{
final Entry[] entryIndex = this.entryIndex;
// update ALL index entries
final int indexSize = this.indexSize;
if(indexSize == 0){ //empty index ALWAYS means empty list, even for trivial index case
throw new IndexOutOfBoundsException("0");
}
// update all index entries (except last one special case)
final int lastIndexEntryIndex = indexSize-1;
for(int i = 0; i < lastIndexEntryIndex; i++){
entryIndex[i] = entryIndex[i].next; //this is also correct if the index entry is the very last list entry
}
// last index entry special case
if((this.size-- & this.indexSpanModulo) == 0){
// if size so far was "even" in terms of the index span, then the last index entry has to be removed
entryIndex[--this.indexSize] = null;
}
else {
// normal case: same as for other indexed entries
entryIndex[lastIndexEntryIndex] = entryIndex[lastIndexEntryIndex].next;
}
// finally disjoin and remove the first entry
final Entry entryToRemove = this.head.next; //head still knows the "still first" entry
final E value = (E)entryToRemove.value;
this.releaseIntermediateEntry(entryToRemove); //first entry is intermediate beteween head and second entry
return value;
}
@SuppressWarnings("unchecked")
public E removeLast()
{
if(this.size == 0){
throw new IndexOutOfBoundsException();
}
// last index entry special case
if((this.size-- & this.indexSpanModulo) == 0){
// if size so far was "even" in terms of the index span, then the last index entry has to be removed
this.entryIndex[--this.indexSize] = null;
}
else {
// normal case: right shift the last index entry
final int lastIndexEntryIndex = this.indexSize-1;
this.entryIndex[lastIndexEntryIndex] = this.entryIndex[lastIndexEntryIndex].next;
}
final Entry entryToRemove = this.head.prev; //head still knows the "still first" entry
final E value = (E)entryToRemove.value;
entryToRemove.prev.next = null; //head is never referenced in forward direction
this.head.prev = entryToRemove.prev;
if(this.cacheSize < this.cache.length){
//return entry back to the pool
this.cache[this.cacheSize++] = entryToRemove;
//reset the entry and enable GC to collect the stuff entry references so far
entryToRemove.value = null;
// entryToRemove.next = null; //must have been null anyway
entryToRemove.prev = null;
}
//otherwise just let the nwo unrefernced entry lay around and wait for GC to kill it
return value;
}
@SuppressWarnings("unchecked")
public E remove(final int index)
{
if(index == 0){
return this.removeFirst(); //does the bounds check itself
}
else if(index+1 >= this.size){
if(index >= this.size){
throw new IndexOutOfBoundsException(Integer.toString(index));
}
return this.removeLast();
}
// index being actually intermediate is ensured here
// determine index entry index for finding the entry to remove and for later index update
final int indexIndex = index>>this.indexSpanExponent;
// find the entry to be removed
final Entry[] entryIndex = this.entryIndex;
Entry hopEntry = entryIndex[indexIndex];
final int neededHops = index & this.indexSpanModulo;
for(int hops = neededHops; hops --> 0;){
hopEntry = hopEntry.next;
}
final Entry entryToRemove = hopEntry; // remember the found to-be-removed entry for later removal
/* update the index entry responsible for the to-be-removed entry
* if exactely one hop was needed earlier, then skip the to-be-removed entry
*/
entryIndex[indexIndex] = neededHops == 1 ?entryToRemove.next :entryIndex[indexIndex].next;
// update all remaining index entries (except last one special case)
final int lastIndexEntryIndex = this.indexSize-1;
for(int i = indexIndex+1; i < lastIndexEntryIndex; i++){
entryIndex[i] = entryIndex[i].next; //this is also correct if the index entry is the very last list entry
}
// last index entry special case
if((this.size-- & this.indexSpanModulo) == 0){
// if size so far was "even" in terms of the index span, then the last index entry has to be removed
entryIndex[--this.indexSize] = null;
}
else {
// normal case: same as for other indexed entries
entryIndex[lastIndexEntryIndex] = entryIndex[lastIndexEntryIndex].next;
}
final E oldValue = (E)entryToRemove.value;
this.releaseIntermediateEntry(entryToRemove); //remove entry AFTER the index update, because index update check may need it.
return oldValue;
}
@SuppressWarnings("unchecked")
public E getFirst()
{
return (E)this.head.next.value;
}
@SuppressWarnings("unchecked")
public E getLast()
{
return (E)this.head.prev.value;
}
public void setFirst(final E element)
{
this.head.next.value = element;
}
public void setLast(final E element)
{
this.head.prev.value = element;
}
/**
* Note: setting index size to one before and back to some higher value after the call to this method can speed
* up the operation.
*
* @param elements
* @return
*/
public VarList addIterable(final Iterable extends E> elements)
{
for(final E t : elements) {
//can't foresee elements' size, so do it the hard way
this.add(t);
}
return this;
}
/**
* Note: setting index size to one before and back to some higher value after the call to this method can speed
* up the operation.
*
* @param index
* @param elements
* @return
*/
public VarList insertIterable(int index, final Iterable extends E> elements)
{
for(final E t : elements) {
//can't foresee elements' size, so do it the hard way
this.add(index++, t);
}
return this;
}
/**
* Special version of Mergesort that sorts the list in place without any additional memory allocation or
* recursion stacktrace.
* Performance comparison to normal Mergesort with array allocation:
*
* - Considerably faster for small list sizes.
* - Considerably slower for large list sizes.
*
* (where "small" and "large" depends on hardware. Threshold was 10000 in tests.)
*
* @param comparator
* @return
*/
@SuppressWarnings("unchecked")
public VarList sortInPlace(final Comparator comparator)
{
if(this.size <= 1) return this;
this.mergeSortInPlace((Comparator