/**
*
*/
package net.varkhan.base.containers.list;
import net.varkhan.base.containers.*;
import net.varkhan.base.containers.Iterable;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.NoSuchElementException;
/**
* <b>Generic IndexedList using sparse segments.</b>.
* <p/>
* This list stores objects in arbitrary positions (their index, in the sense of
* {@link Indexed}).
* <p/>
* The entries are kept in a resizable array of storage segments. Each segment
* maintains occupancy statistics in a bit array, and a compact array of actual
* elements.
* <p/>
* The segment structure bypasses the normal size limits implied by the {@code int}
* subscript, and limits reallocation costs.
* <p/>
*
* @author varkhan
* @date Mar 12, 2009
* @time 6:16:09 AM
*/
public class SparseIndexedList<Type> extends AbstractSparseIndexedList implements IndexedList<Type>, Externalizable, Cloneable {
private static final long serialVersionUID=1L;
/**
* The list slot storage
*/
private Object[][] list=null;
/**
* The default return value
*/
private Type defVal=null;
/**********************************************************************************
** List constructors
**/
/**
* Creates a new SparseIndexedList, specifying the reallocation strategy.
*
* @param blockshift the node reference storage block size 2-logarithm
* @param growthfact the node reference storage growth factor
*/
public SparseIndexedList(int blockshift, double growthfact) {
super(blockshift, growthfact);
}
/**
* Creates a new SparseIndexedList
*/
public SparseIndexedList() {
this(10, 1.5);
}
/**
* Copies an IndexedList, specifying the reallocation strategy.
*
* @param blockshift the node reference storage block size 2-logarithm
* @param growthfact the node reference storage growth factor
* @param list the IndexedList to copy
*/
public SparseIndexedList(int blockshift, double growthfact, IndexedList<Type> list) {
this();
Index it=list.indexes();
while(it.hasNext()) {
long id=it.next();
Type obj=list.get(id);
if(obj!=null) set(id, obj);
}
}
/**
* Creates a new SparseIndexedList.
*
* @param list the IndexedList to copy
*/
public SparseIndexedList(IndexedList<Type> list) {
this();
Index it=list.indexes();
while(it.hasNext()) {
long id=it.next();
Type obj=list.get(id);
if(obj!=null) set(id, obj);
}
}
/**
* Builds a SparseIndexedList from an array.
*
* @param array the array to copy
*/
public SparseIndexedList(Type... array) {
this();
for(int id=0;id<array.length;id++) {
Type obj=array[id];
if(obj!=null) set(id, obj);
}
}
/**********************************************************************************
** List statistics accessors
**/
/**
* Deletes all elements from this list.
*/
public void clear() {
super.clear();
list=null;
}
/**
* Gets the default value.
*
* @return the default value, returned by {@link #get} on empty entries
*/
public Type getDefaultValue() {
return defVal;
}
/**
* Sets the default value.
*
* @param def the default value, returned by {@link #get} on empty entries
*/
public void setDefaultValue(Type def) {
defVal=def;
}
/**********************************************************************************
** List entries accessors
**/
/**
* Extracts the element designated by an index.
*
* @param index a unique identifier for this entry
*
* @return the requested element, or {@literal null} if no entry is associated to this index
*/
@SuppressWarnings( { "unchecked" })
public Type get(long index) {
if(index<0||index>=head) return defVal;
int blockpos=(int) (index>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) return defVal;
int pos=getSparsePos(mask, (int) (index&blockmask));
if(pos<=0) return defVal;
return (Type) list[blockpos][pos-1];
}
/**
* Adds an element at the end of the list.
* <p/>
* Note: this is equivalent to {@link #set}{@code (head(), val)}, and
* the returned index is the value of {@link #head()} before the call.
*
* @param val the object to store in the list
*
* @return the entry's unique identifier, that will subsequently give access to the element
*/
public long add(Type val) {
long index=head;
set(index, val);
return index;
}
/**
* Associates an element to a particular index (the index can refer to an
* already existing entry, in which case that entry is overwritten).
*
* @param index a unique identifier for this entry
* @param val the object to store in the list
*
* @return the entry's unique identifier, equal to {@code index}, or {@literal -1L} on error
*/
public long set(long index, Type val) {
int blockpos=(int) (index>>>blockshift);
if(bits==null||list==null) {
bits=new byte[blocksize][];
list=new Object[blocksize][];
}
else if(blockpos>=list.length) {
// We need to realloc
int newBlockNum=(int) ((list.length+1)*growthfact);
if(newBlockNum<=blockpos) newBlockNum=blockpos+1;
byte[][] newbits=new byte[newBlockNum][];
System.arraycopy(bits, 0, newbits, 0, bits.length);
bits=newbits;
Object[][] newlist=new Object[newBlockNum][];
System.arraycopy(list, 0, newlist, 0, list.length);
list=newlist;
}
byte[] mask=bits[blockpos];
if(mask==null) {
mask=new byte[blockhead];
bits[blockpos]=mask;
}
int blockoff=(int) (index&blockmask);
Object[] block=list[blockpos];
if(block==null) {
block=new Object[1];
list[blockpos]=block;
block[0]=val;
setBitOn(mask, blockoff);
size++;
if(index>=head) head=index+1;
return index;
}
int blockidx=getSparsePos(mask, blockoff);
if(blockidx>0) {
// We already have an object here... set it and bail out
block[blockidx-1]=val;
if(index>=head) head=index+1;
return index;
}
blockidx=-blockidx;
int len=getSparseLength(mask);
if(len>=block.length) {
int newlen=(int) ((block.length+1)*growthfact);
if(len>newlen) newlen=len;
if(newlen>blocksize) newlen=blocksize;
Object[] newblock=new Object[newlen];
System.arraycopy(block, 0, newblock, 0, blockidx);
System.arraycopy(block, blockidx, newblock, blockidx+1, len-blockidx);
newblock[blockidx]=val;
list[blockpos]=newblock;
}
else {
System.arraycopy(block, blockidx, block, blockidx+1, len-blockidx);
block[blockidx]=val;
}
setBitOn(mask, blockoff);
size++;
if(index>=head) head=index+1;
return index;
}
/**
* Deletes an element, and invalidates the related index.
*
* @param index a unique identifier for this entry
*/
public void del(long index) {
if(index<0||index>=head) return;
int blockpos=(int) (index>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) return;
int blockoff=(int) (index&blockmask);
int blockidx=getSparsePos(mask, blockoff);
/// Do we have something to delete?
if(blockidx<=0) return;
Object[] block=list[blockpos];
if(block==null) return;
setBitOff(mask, blockoff);
// This should be the original length -1
int len=getSparseLength(mask);
if(len==0) {
list[blockpos]=null;
bits[blockpos]=null;
}
else {
// Set blockidx to the actual zero-based position of the object
blockidx--;
// The original length of the section was len+1
if(len*growthfact*growthfact<block.length) {
int newlen=(int) ((block.length+1)/growthfact);
if(len>newlen) newlen=len;
if(newlen>blocksize) newlen=blocksize;
Object[] newblock=new Object[newlen];
// Copy the start section, up to before blockidx
System.arraycopy(block, 0, newblock, 0, blockidx);
// Copy the end section, deleting the object at blockidx
System.arraycopy(block, blockidx+1, newblock, blockidx, len-blockidx);
list[blockpos]=newblock;
}
else {
// Copy the end section, deleting the object at blockidx
System.arraycopy(block, blockidx+1, block, blockidx, len-blockidx);
}
// Decrement the ref count of the last object
block[len]=null;
}
size--;
int headpos=(int) ((head-1)>>>blockshift);
while(headpos>=0&&isEmpty(bits[headpos])) headpos--;
if(headpos<0) {
head=0;
if(blocksize*growthfact<list.length) {
// We need to realloc
bits=new byte[blocksize][];
list=new Object[blocksize][];
}
}
else {
head=(headpos<<blockshift)+getSparseHead(bits[headpos]);
if(headpos*growthfact*growthfact<list.length) {
// We need to realloc
int newBlockNum=(int) ((list.length+1)/growthfact);
byte[][] newbits=new byte[newBlockNum][];
System.arraycopy(bits, 0, newbits, 0, headpos+1);
bits=newbits;
Object[][] newlist=new Object[newBlockNum][];
System.arraycopy(list, 0, newlist, 0, headpos+1);
list=newlist;
}
}
}
/**********************************************************************************
** List entries iterators
**/
/**
* Iterates over all elements in the list.
*
* @return an iterator over all the elements stored in the list
*/
public Iterator<? extends Type> iterator() {
return new Iterator<Type>() {
private long index=-1;
public boolean hasNext() {
long i=index+1;
while(i<head) {
int blockpos=(int) (i>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) {
i=(blockpos+1)<<blockshift;
continue;
}
int pos=getSparsePos(mask, (int) (i&blockmask));
if(pos>0) return true;
i++;
}
return false;
}
@SuppressWarnings("unchecked")
public Type next() {
index++;
while(index<head) {
int blockpos=(int) (index>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) {
index=(blockpos+1)<<blockshift;
continue;
}
int pos=getSparsePos(mask, (int) (index&blockmask));
if(pos>0) return (Type) list[blockpos][pos-1];
index++;
}
throw new NoSuchElementException("No element at index "+index);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Iterate over each element of the list, and pass it as argument to a
* visitor's {@link Visitor#invoke} method, until this method returns
* a negative count.
*
* @param vis the visitor
* @param par the control parameter
* @param <Par> the type of the control parameter
*
* @return the sum of all positive return values from the visitor
*/
public <Par> long visit(Visitor<Type,Par> vis, Par par) {
long c=0;
long i=0;
while(i<head) {
int blockpos=(int) (i>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) {
i=(blockpos+1)<<blockshift;
continue;
}
int pos=getSparsePos(mask, (int) (i&blockmask));
if(pos>0) {
@SuppressWarnings("unchecked")
Type obj=(Type) list[blockpos][pos-1];
long r=vis.invoke(obj, par);
if(r<0) return c;
c+=r;
}
i++;
}
return c;
}
/**
* Iterate over each element of the list, and pass it as argument to a
* visitor's {@link IndexedVisitor#invoke} method, until this method returns
* a negative count.
*
* @param vis the visitor
* @param par the control parameter
* @param <Par> the type of the control parameter
*
* @return the sum of all positive return values from the visitor
*/
public <Par> long visit(IndexedVisitor<Type,Par> vis, Par par) {
long c=0;
long i=0;
while(i<head) {
int blockpos=(int) (i>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) {
i=(blockpos+1)<<blockshift;
continue;
}
int pos=getSparsePos(mask, (int) (i&blockmask));
if(pos>0) {
@SuppressWarnings("unchecked")
Type obj=(Type) list[blockpos][pos-1];
long r=vis.invoke(i, obj, par);
if(r<0) return c;
c+=r;
}
i++;
}
return c;
}
/**
* Iterates over a set of elements designated by an array of indexes.
*
* @param indexes an array of identifiers
*
* @return an iterable over all the elements indexed by the identifiers
*/
public Iterable<? extends Type> iterate(final long[] indexes) {
return new Iterable<Type>() {
public Iterator<Type> iterator() {
return new Iterator<Type>() {
private int i=0;
public boolean hasNext() {
return i<indexes.length;
}
@SuppressWarnings("unchecked")
public Type next() {
long index=indexes[i++];
if(index<0||index>=head) return null;
int blockpos=(int) (index>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) return null;
int pos=getSparsePos(mask, (int) (index&blockmask));
if(pos<=0) return null;
return (Type) list[blockpos][pos-1];
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
/**
* Iterates over a set of elements designated by an iterator over indexes.
*
* @param indexes an iterable over identifiers
*
* @return an iterable over all the elements indexed by the identifiers
*/
public Iterable<? extends Type> iterate(final java.lang.Iterable<Long> indexes) {
return new Iterable<Type>() {
public Iterator<Type> iterator() {
return new Iterator<Type>() {
private final java.util.Iterator<Long> iter=indexes.iterator();
public boolean hasNext() {
return iter.hasNext();
}
@SuppressWarnings("unchecked")
public Type next() {
long index=iter.next();
if(index<0||index>=head) return null;
int blockpos=(int) (index>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) return null;
int pos=getSparsePos(mask, (int) (index&blockmask));
if(pos<=0) return null;
return (Type) list[blockpos][pos-1];
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
/**
* Iterates over a set of elements designated by an iterator over indexes.
*
* @param indexes an iterable over identifiers
*
* @return an iterable over all the elements indexed by the identifiers
*/
public Iterable<? extends Type> iterate(final Indexable indexes) {
return new Iterable<Type>() {
public Iterator<Type> iterator() {
return new Iterator<Type>() {
private final Index iter=indexes.indexes();
public boolean hasNext() {
return iter.hasNext();
}
@SuppressWarnings("unchecked")
public Type next() {
long index=iter.next();
if(index<0||index>=head) return null;
int blockpos=(int) (index>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) return null;
int pos=getSparsePos(mask, (int) (index&blockmask));
if(pos<=0) return null;
return (Type) list[blockpos][pos-1];
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
/**********************************************************************************
** Externalization
**/
/**
* Write a SparseIndexedList to a stream.
*
* @param out the stream to write the object to
*
* @throws IOException if I/O errors occur
* @serialData <li/> {@code Object defVal} - the default value
* <li/> {@code byte blockshift} - the block size 2-logarithm
* <li/> {@code double growthfact} - the buffer growth factor
* <li/> {@code long size} - the number of set entries
* <li/> {@code long head} - the highest allocated index + 1
* <li/> all the blocks, as a bit mask of (blocksize/8) {@code byte}s,
* followed by the set values (a variable number of {@code float}s, as defined by the mask)
*/
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
out.writeObject(defVal);
if(head>0) {
int headlen=(int) (head>>>blockshift)+1;
byte[] zero=new byte[blockhead];
for(int i=0;i<headlen;i++) {
final byte[] mask=bits[i];
if(mask==null) {
out.write(zero, 0, blockhead);
}
else {
out.write(mask, 0, blockhead);
int len=getSparseLength(mask);
final Object[] block=list[i];
for(int j=0;j<len;j++) out.writeObject(block[j]);
}
}
}
}
/**
* Read a SparseIndexedList from a stream.
*
* @param in the stream to read the object from
*
* @throws IOException if I/O errors occur
*/
@SuppressWarnings( { "unchecked" })
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
defVal=(Type) in.readObject();
if(head>0) {
int headlen=(int) (head>>>blockshift)+1;
bits=new byte[headlen][];
list=new Object[headlen][];
for(int i=0;i<headlen;i++) {
final byte[] mask=bits[i]=new byte[blockhead];
in.readFully(mask, 0, blockhead);
int len=getSparseLength(mask);
if(len==0) {
bits[i]=null;
list[i]=null;
}
else {
final Object[] block=list[i]=new Object[len];
for(int j=0;j<len;j++) block[j]=in.readObject();
}
}
}
}
/**********************************************************************************
** Object method overrides
**/
/**
* Return a hash code value for this list.
*
* @return a hash code
*/
public int hashCode() {
int hash=blockshift;
hash^=size^(size>>>32);
hash^=head^(head>>>32);
if(head>0) {
int headlen=(int) (head>>>blockshift)+1;
for(int i=0;i<headlen;i++) {
final byte[] mask=bits[i];
if(mask!=null) {
for(int j=0;j<blockhead;) {
int f=(mask[j++]&0xFF)<<24;
if(j<blockhead) f+=(mask[j++]&0xFF)<<16;
if(j<blockhead) f+=(mask[j++]&0xFF)<<8;
if(j<blockhead) f+=(mask[j++]&0xFF)<<0;
hash^=f;
}
int len=getSparseLength(mask);
final Object[] block=list[i];
for(int j=0;j<len;j++) {
Object val=block[j];
if(val!=null) hash^=val.hashCode();
}
}
}
}
return hash;
}
public boolean equals(Object obj) {
if(!(obj instanceof SparseIndexedList)) return false;
SparseIndexedList<?> that=(SparseIndexedList<?>) obj;
if(this.size!=that.size) return false;
if(this.head!=that.head) return false;
if(this.blockshift!=that.blockshift) return false;
if(this.defVal!=that.defVal) return false;
if(head>0) {
int headlen=(int) (head>>>blockshift)+1;
for(int i=0;i<headlen;i++) {
final byte[] thismask=this.bits[i];
final byte[] thatmask=that.bits[i];
int len;
if(thismask==null||(len=getSparseLength(thismask))==0) {
if(thatmask!=null&&getSparseLength(thatmask)>0) return false;
continue;
}
for(int j=0;j<blockhead;j++) if(thismask[j]!=thatmask[j]) return false;
final Object[] thisblock=this.list[i];
final Object[] thatblock=that.list[i];
if(thisblock==null&&thatblock==null) continue;
if(thisblock==null||thatblock==null) return false;
for(int j=0;j<len;j++) {
Object thisval=thisblock[j];
Object thatval=thatblock[j];
if(thisval==thatval) continue;
if(thisval==null || thatval==null) return false;
if(!thisval.equals(thatval)) return false;
}
}
}
return true;
}
@SuppressWarnings("unchecked")
public SparseIndexedList<Type> clone() {
SparseIndexedList<Type> clone;
try { clone=(SparseIndexedList<Type>) super.clone(); }
catch(CloneNotSupportedException e) { return null; }
if(this.bits!=null) {
clone.bits=new byte[this.bits.length][];
for(int i=0;i<this.bits.length;i++) {
byte[] mask=this.bits[i];
if(mask!=null) {
byte[] m=new byte[mask.length];
System.arraycopy(mask, 0, m, 0, mask.length);
clone.bits[i]=m;
}
}
}
if(this.list!=null) {
clone.list=new Object[this.list.length][];
for(int i=0;i<this.list.length;i++) {
Object[] block=this.list[i];
if(block!=null) {
Object[] b=new Object[block.length];
System.arraycopy(block, 0, b, 0, block.length);
clone.list[i]=b;
}
}
}
return clone;
}
/**
* Returns a string representation of the IndexedList.
*
* @return a string enclosing in square brackets the string representations
* of all the elements in the list, prefixed by their index
*/
public String toString() {
StringBuilder buf=new StringBuilder();
buf.append('[');
boolean first = true;
long i=0;
while(i<head) {
int blockpos=(int) (i>>>blockshift);
byte[] mask=bits[blockpos];
if(mask==null) {
i=(blockpos+1)<<blockshift;
continue;
}
int pos=getSparsePos(mask, (int) (i&blockmask));
if(pos>0) {
if(first) first=false;
else buf.append(',');
buf.append(' ').append(i).append('@').append(list[blockpos][pos-1]);
}
i++;
}
buf.append(' ').append('(').append(defVal).append(')').append(' ');
buf.append(']');
return buf.toString();
}
}