convex.core.data.SetLeaf Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of convex-core Show documentation
Show all versions of convex-core Show documentation
Convex core libraries and common utilities
The newest version!
package convex.core.data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.BiFunction;
import convex.core.exceptions.BadFormatException;
import convex.core.exceptions.InvalidDataException;
import convex.core.exceptions.TODOException;
import convex.core.util.Utils;
/**
* Limited size Persistent Merkle Set implemented as a small sorted list of
* Values
*
* Must be sorted by Key hash value to ensure uniqueness of representation
*
* @param Type of values
*/
public class SetLeaf extends AHashSet {
/**
* Maximum number of elements in a SetLeaf
*/
public static final int MAX_ELEMENTS = 16;
private final Ref[] elements;
SetLeaf(Ref[] items) {
super(items.length);
elements = items;
}
/**
* Creates a SetLeaf with the specified elements.
*
* Null entries are ignored/removed.
*
* @param elements Refs of Elements to include
* @return New ListMap
*/
@SafeVarargs
public static SetLeaf create(Ref... elements) {
return create(elements, 0, elements.length);
}
/**
* Create a SetLeaf with raw element Refs. Can create an invalid Cell, useful mainly for testing
* @param refs Refs to set elements, in desired order
* @return SetLeaf instance, possibly invalid
*/
@SuppressWarnings("unchecked")
public static SetLeaf unsafeCreate(Ref... refs) {
return new SetLeaf(refs);
}
/**
* Create a SetLeaf with raw elements. Can create an invalid Cell, useful mainly for testing
* @param elements Elements to include in set, in desired order
* @return SetLeaf instance, possibly invalid
*
*/
@SuppressWarnings("unchecked")
public static SetLeaf unsafeCreate(V... elements) {
int n=elements.length;
Ref[] refs=new Ref[n];
for (int i=0; i SetLeaf create(Ref[] entries, int offset, int length) {
if (length == 0) return Sets.empty();
if (length > MAX_ELEMENTS) throw new IllegalArgumentException("Too many elements: " + entries.length);
Ref[] sorted = Utils.copyOfRangeExcludeNulls(entries, offset, offset + length);
if (sorted.length == 0) return Sets.empty();
Arrays.sort(sorted);
return new SetLeaf(sorted);
}
@SuppressWarnings("unchecked")
public static SetLeaf create(V item) {
return new SetLeaf(new Ref[] { Ref.get(item) });
}
@Override
public Ref getValueRef(ACell k) {
// Use cached hash if available
Hash h=(k==null)?Hash.NULL_HASH:k.cachedHash();
if (h!=null) return getRefByHash(h);
int len = size();
for (int i = 0; i < len; i++) {
Ref e = elements[i];
if (Cells.equals(k, e.getValue())) return e;
}
return null;
}
@Override
public boolean containsHash(Hash hash) {
return getRefByHash(hash) != null;
}
@Override
protected Ref getRefByHash(Hash hash) {
int len = size();
for (int i = 0; i < len; i++) {
Ref e = elements[i];
if (hash.equals(e.getHash())) return e;
}
return null;
}
/**
* Gets the index of key k in the internal array, or -1 if not found
*
* @param key
* @return
*/
private int seek(T key) {
int len = size();
for (int i = 0; i < len; i++) {
if (Cells.equals(key, elements[i].getValue())) return i;
}
return -1;
}
/**
* Gets the index of key k in the internal array, or -1 if not found
*
* @param key
* @return
*/
private int seekKeyRef(Ref> key) {
Hash h=key.getHash();
int len = size();
for (int i = 0; i < len; i++) {
if (h.compareTo(elements[i].getHash())==0) return i;
}
return -1;
}
@SuppressWarnings("unchecked")
@Override
public SetLeaf exclude(ACell key) {
int i = seek((T)key);
if (i < 0) return this; // not found
return excludeAt(i);
}
@Override
public SetLeaf excludeRef(Ref> key) {
int i = seekKeyRef(key);
if (i < 0) return this; // not found
return excludeAt(i);
}
@SuppressWarnings("unchecked")
private SetLeaf excludeAt(int index) {
int len = size();
if (len == 1) return Sets.empty();
Ref[] newEntries = (Ref[]) new Ref[len - 1];
System.arraycopy(elements, 0, newEntries, 0, index);
System.arraycopy(elements, index + 1, newEntries, index, len - index - 1);
return new SetLeaf(newEntries);
}
protected void accumulateValues(ArrayList al) {
for (int i = 0; i < elements.length; i++) {
Ref me = elements[i];
al.add(me.getValue());
}
}
@Override
public int encode(byte[] bs, int pos) {
bs[pos++]=Tag.SET;
return encodeRaw(bs,pos);
}
@Override
public int encodeRaw(byte[] bs, int pos) {
long n=count();
pos = Format.writeVLCLong(bs,pos, n);
for (int i = 0; i < n; i++) {
pos = elements[i].encode(bs, pos);;
}
return pos;
}
@Override
public int estimatedEncodingSize() {
// allow space for header, size byte, 2 refs per entry
return 2 + Format.MAX_EMBEDDED_LENGTH * size();
}
public static int MAX_ENCODING_LENGTH= 2 + MAX_ELEMENTS * Format.MAX_EMBEDDED_LENGTH;
/**
* Reads a MapLeaf from the provided ByteBuffer Assumes the header byte is
* already read.
*
* @param b Blob to read from
* @param pos Start position in Blob (location of tag byte)
* @param count Count of map elements
* @return New decoded instance
* @throws BadFormatException In the event of any encoding error
*/
public static SetLeaf read(Blob b, int pos, long count) throws BadFormatException {
int headerLen=1+Format.getVLCLength(count);
int epos=pos+headerLen;
if (count == 0) return Sets.empty();
if (count < 0) throw new BadFormatException("Negative count of map elements!");
if (count > MAX_ELEMENTS) throw new BadFormatException("SetLeaf too big: " + count);
@SuppressWarnings("unchecked")
Ref[] items = (Ref[]) new Ref[(int) count];
for (int i = 0; i < count; i++) {
Ref ref=Format.readRef(b,epos);
epos+=ref.getEncodingLength();
items[i]=ref;
}
if (!isValidOrder(items)) throw new BadFormatException("Set elements out of order in encoding");
SetLeaf result=new SetLeaf(items);
Blob enc=b.slice(pos, epos);
result.attachEncoding(enc);
return result;
}
@SuppressWarnings("unchecked")
public static SetLeaf emptySet() {
return (SetLeaf) Sets.EMPTY;
}
@Override
public boolean isCanonical() {
return true;
}
@Override public final boolean isCVMValue() {
return true;
}
@Override public final boolean isDataValue() {
return true;
}
private static boolean isValidOrder(Ref[] entries) {
long count = entries.length;
for (int i = 0; i < count - 1; i++) {
Hash a = entries[i].getHash();
Hash b = entries[i + 1].getHash();
if (a.compareTo(b) >= 0) {
return false;
}
}
return true;
}
@Override
public int getRefCount() {
return elements.length;
}
@SuppressWarnings("unchecked")
@Override
public Ref getRef(int i) {
Ref e = elements[i]; // IndexOutOfBoundsException if out of range
return e;
}
@Override
public Ref getElementRef(long i) {
Ref e = elements[Utils.checkedInt(i)]; // Exception if out of range
return e;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public SetLeaf updateRefs(IRefFunction func) {
int n = elements.length;
if (n == 0) return this;
Ref[] newEntries = elements;
for (int i = 0; i < n; i++) {
Ref e = newEntries[i];
Ref newEntry = (Ref) func.apply(e);
if (e!=newEntry) {
if (newEntries==elements) newEntries=elements.clone();
newEntries[i]=newEntry;
}
}
if (newEntries==elements) return this;
// Note: we assume no key hashes have changed
return new SetLeaf(newEntries);
}
/**
* Filters this ListMap to contain only key hashes with the hex digits specified
* in the given Mask
*
* @param digitPos Position of the hex digit to filter
* @param mask Mask of digits to include
* @return Filtered ListMap
*/
public SetLeaf filterHexDigits(int digitPos, int mask) {
mask = mask & 0xFFFF;
if (mask == 0) return Sets.empty();
if (mask == 0xFFFF) return this;
int sel = 0;
int n = size();
for (int i = 0; i < n; i++) {
Hash h = elements[i].getHash();
if ((mask & (1 << h.getHexDigit(digitPos))) != 0) {
sel = sel | (1 << i); // include this index in selection
}
}
if (sel == 0) return Sets.empty(); // no entries selected
return filterEntries(sel);
}
/**
* Filters entries using the given bit mask
*
* @param selection
* @return
*/
@SuppressWarnings("unchecked")
private SetLeaf filterEntries(int selection) {
if (selection == 0) return Sets.empty(); // no items selected
int n = size();
if (selection == ((1 << n) - 1)) return this; // all items selected
Ref[] newEntries = new Ref[Integer.bitCount(selection)];
int ix = 0;
for (int i = 0; i < n; i++) {
if ((selection & (1 << i)) != 0) {
newEntries[ix++] = elements[i];
}
}
assert (ix == Integer.bitCount(selection));
return new SetLeaf(newEntries);
}
@Override
public AHashSet mergeWith(AHashSet b, int setOp) {
return mergeWith(b,setOp,0);
}
@Override
protected AHashSet mergeWith(AHashSet b, int setOp, int shift) {
if (b instanceof SetLeaf) return mergeWith((SetLeaf) b, setOp,shift);
if (b instanceof SetTree) return b.mergeWith(this, reverseOp(setOp),shift);
throw new TODOException("Unhandled map type: " + b.getClass());
}
public AHashSet mergeWith(SetLeaf b, int setOp,int shift) {
if (this.equals(b)) return applySelf(setOp); // no change in identical case
int al = this.size();
int bl = b.size();
int ai = 0;
int bi = 0;
ArrayList> results = null;
while ((ai < al) || (bi < bl)) {
Ref ae = (ai < al) ? this.elements[ai] : null;
Ref be = (bi < bl) ? b.elements[bi] : null;
int c = (ae == null) ? 1 : ((be == null) ? -1 : ae.getHash().compareTo(be.getHash()));
Ref newE;
if (c==0) {
newE= applyOp(setOp,ae,be);
} else if (c<0) {
// apply to a
newE= applyOp(setOp,ae,null);
} else {
// apply to a
newE= applyOp(setOp,null,be);
}
// Create results arraylist if any difference from this
if ((results == null) && (newE != ((c <= 0) ? ae : null))) {
// create new results array if difference detected
results = new ArrayList<>(2*MAX_ELEMENTS); // space for all if needed
// include new entries
for (int i = 0; i < ai; i++) {
results.add(elements[i]);
}
}
if (c <= 0) ai++; // inc ai if we used ae
if (c >= 0) bi++; // inc bi if we used be
if ((results != null) && (newE != null)) results.add(newE);
}
if (results == null) return this;
return Sets.createWithShift(shift,results);
}
public R reduceValues(BiFunction super R, ? super T, ? extends R> func, R initial) {
int n = size();
R result = initial;
for (int i = 0; i < n; i++) {
result = func.apply(result, elements[i].getValue());
}
return result;
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(ACell a) {
if (!(a instanceof SetLeaf)) return false;
return equals((SetLeaf) a);
}
public boolean equals(SetLeaf a) {
if (this == a) return true;
if (count != a.count) return false;
int n = (int)count;
for (int i = 0; i < n; i++) {
if (!elements[i].equals(a.elements[i])) return false;
}
return true;
}
@Override
public void validate() throws InvalidDataException {
super.validate();
validateWithPrefix(Hash.EMPTY_HASH,0,-1);
}
@Override
protected void validateWithPrefix(Hash prefix, int digit, int position) throws InvalidDataException {
if (!isValidOrder(elements)) {
throw new InvalidDataException("Bad ordering of set elements!",this);
}
for (int i = 0; i < elements.length; i++) {
Ref e = elements[i];
Hash h = e.getHash();
long match=h.hexMatch(prefix);
if (match<(position-1)) {
throw new InvalidDataException("Parent prefix did not match",this);
}
if (position>=0) {
int mydigit=h.getHexDigit(position);
if (mydigit!=digit) {
throw new InvalidDataException("Bad hex digit at position: "+position,this);
}
}
e.validate();
T value=e.getValue();
if(!Cells.isCVM(value)) throw new InvalidDataException("Non-CVM value in Set",this);
}
}
@Override
public void validateCell() throws InvalidDataException {
if ((count == 0) && (this != Sets.EMPTY)) {
throw new InvalidDataException("Empty set not using canonical instance", this);
}
if (count > MAX_ELEMENTS) {
throw new InvalidDataException("Too many items in SetLeaf: " + elements.length, this);
}
// validates both key uniqueness and sort order
if (!isValidOrder(elements)) throw new InvalidDataException("Bad ordering of set elements",this);
}
@Override
public boolean containsAll(ASet> b) {
if (this==b) return true;
// if set is too big, can't possibly contain all keys
if (b.count()>count) return false;
// must be a setleaf if this size or smaller
return containsAll((SetLeaf>)b);
}
@Override
public boolean isSubset(ASet super T> b) {
return b.containsAll(this);
}
protected boolean containsAll(SetLeaf> b) {
int ix=0;
for (Ref> meb:b.elements) {
Hash bh=meb.getHash();
if (ix>=count) return false; // no remaining entries in this
while (ix mea=elements[ix];
Hash ah=mea.getHash();
int c=ah.compareTo(bh);
if (c<0) {
// ah is smaller than bh
// need to advance ix and try next entry
ix++;
if (ix>=count) return false; // not found
continue;
} else if (c>0) {
return false; // didn't contain the key entry
} else {
// found it, so advance to next entry in b and update ix
ix++;
break;
}
}
}
return true;
}
@Override
public AHashSet includeRef(Ref ref) {
return includeRef(ref,0);
}
@Override
protected AHashSet includeRef(Ref e, int shift) {
int n=elements.length;
Hash h=e.getHash();
int pos=0;
for (; pos iref=elements[pos];
int c=h.compareTo(iref.getHash());
if (c==0) return this;
if (c<0) break; // need to add at this position
}
// New element must be added at pos
@SuppressWarnings("unchecked")
Ref[] newEntries=new Ref[n+1];
System.arraycopy(elements, 0, newEntries, 0, pos);
System.arraycopy(elements, pos, newEntries, pos+1, n-pos);
newEntries[pos]=e;
if (n(newEntries);
} else {
// Maximum size exceeded, so need to expand to tree.
// Shift required since this might not be the tree root!
return SetTree.create(newEntries, shift);
}
}
@SuppressWarnings("unchecked")
@Override
protected void copyToArray(R[] arr, int offset) {
for (int i=0; i toCanonical() {
if (count<=MAX_ELEMENTS) return this;
return SetTree.create(elements, 0);
}
@SuppressWarnings("unchecked")
@Override
public ASet slice(long start, long end) {
if (start<0) return null;
if (end>count) return null;
int n=(int)(end-start);
if (n==count) return this;
if (n==0) return empty();
Ref[] nrefs=new Ref[n];
System.arraycopy(elements, (int) start, nrefs, 0, n);
return new SetLeaf(nrefs);
}
}