Skip to content

Commit

Permalink
Implement support for setting the missing value via a flag
Browse files Browse the repository at this point in the history
  • Loading branch information
RichardWarburton committed Jan 31, 2017
1 parent 37d5b1f commit 5a5323d
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 81 deletions.
100 changes: 61 additions & 39 deletions src/main/java/org/agrona/collections/IntHashSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@ public final class IntHashSet extends AbstractSet<Integer>

private final float loadFactor;
@DoNotSub private int resizeThreshold;
@DoNotSub private int size;
// NB: excludes missing value
@DoNotSub private int sizeOfArrayValues;

private int[] values;
private final IntIterator iterator;
private final IntIterator iterator = new IntHashSetIterator();
private boolean containsMissingValue;

public IntHashSet()
{
Expand All @@ -78,14 +80,11 @@ public IntHashSet(
validateLoadFactor(loadFactor);

this.loadFactor = loadFactor;
size = 0;
sizeOfArrayValues = 0;
@DoNotSub final int capacity = BitUtil.findNextPositivePowerOfTwo(initialCapacity);
resizeThreshold = (int)(capacity * loadFactor); // @DoNotSub
values = new int[capacity];
Arrays.fill(values, MISSING_VALUE);

// NB: references values in the constructor, so must be assigned after values
iterator = new IntHashSetIterator(values);
}

/**
Expand All @@ -107,8 +106,11 @@ public boolean add(final int value)
{
if (value == MISSING_VALUE)
{
throw new IllegalArgumentException("Cannot accept missingValue");
final boolean previousContainsMissingValue = this.containsMissingValue;
containsMissingValue = true;
return !previousContainsMissingValue;
}

final int[] values = this.values;
@DoNotSub final int mask = values.length - 1;
@DoNotSub int index = Hashing.hash(value, mask);
Expand All @@ -124,9 +126,9 @@ public boolean add(final int value)
}

values[index] = value;
size++;
sizeOfArrayValues++;

if (size > resizeThreshold)
if (sizeOfArrayValues > resizeThreshold)
{
increaseCapacity();
}
Expand All @@ -139,7 +141,7 @@ private void increaseCapacity()
@DoNotSub final int newCapacity = values.length * 2;
if (newCapacity < 0)
{
throw new IllegalStateException("Max capacity reached at size=" + size);
throw new IllegalStateException("Max capacity reached at size=" + size());
}

rehash(newCapacity);
Expand Down Expand Up @@ -187,6 +189,13 @@ public boolean remove(final Object value)
*/
public boolean remove(final int value)
{
if (value == MISSING_VALUE)
{
final boolean previousContainsMissingValue = this.containsMissingValue;
containsMissingValue = false;
return previousContainsMissingValue;
}

final int[] values = this.values;
@DoNotSub final int mask = values.length - 1;
@DoNotSub int index = Hashing.hash(value, mask);
Expand All @@ -197,7 +206,7 @@ public boolean remove(final int value)
{
values[index] = MISSING_VALUE;
compactChain(index);
size--;
sizeOfArrayValues--;
return true;
}

Expand Down Expand Up @@ -262,22 +271,25 @@ public boolean contains(final Object value)
*/
public boolean contains(final int value)
{
if (value != MISSING_VALUE)
if (value == MISSING_VALUE)
{
final int[] values = this.values;
@DoNotSub final int mask = values.length - 1;
@DoNotSub int index = Hashing.hash(value, mask);
return containsMissingValue;
}

while (values[index] != MISSING_VALUE)
{
if (values[index] == value)
{
return true;
}
final int[] values = this.values;
@DoNotSub final int mask = values.length - 1;
@DoNotSub int index = Hashing.hash(value, mask);

index = next(index, mask);
while (values[index] != MISSING_VALUE)
{
if (values[index] == value)
{
return true;
}

index = next(index, mask);
}

return false;
}

Expand All @@ -286,15 +298,15 @@ public boolean contains(final int value)
*/
@DoNotSub public int size()
{
return size;
return sizeOfArrayValues + (containsMissingValue ? 1 : 0);
}

/**
* {@inheritDoc}
*/
public boolean isEmpty()
{
return size == 0;
return size() == 0;
}

/**
Expand Down Expand Up @@ -323,7 +335,7 @@ public int capacity()
public void clear()
{
Arrays.fill(values, MISSING_VALUE);
size = 0;
sizeOfArrayValues = 0;
}

/**
Expand Down Expand Up @@ -393,7 +405,7 @@ public IntHashSet difference(final IntHashSet other)
{
if (difference == null)
{
difference = new IntHashSet(size);
difference = new IntHashSet(sizeOfArrayValues);
}

difference.add(value);
Expand Down Expand Up @@ -450,7 +462,7 @@ private static <T> boolean disjunction(final Collection<T> coll, final Predicate
*/
public IntIterator iterator()
{
iterator.reset();
iterator.reset(values, containsMissingValue);

return iterator;
}
Expand All @@ -466,7 +478,7 @@ public void copy(final IntHashSet that)
}

System.arraycopy(that.values, 0, this.values, 0, this.values.length);
this.size = that.size;
this.sizeOfArrayValues = that.sizeOfArrayValues;
}

/**
Expand All @@ -492,7 +504,7 @@ public <T> T[] toArray(final T[] into)
throw new ArrayStoreException("Cannot store Integers in array of type " + componentType);
}

@DoNotSub final int size = this.size;
@DoNotSub final int size = size();
final T[] arrayCopy = into.length >= size ? into : (T[])Array.newInstance(componentType, size);
copyValues(arrayCopy);

Expand All @@ -504,9 +516,8 @@ public <T> T[] toArray(final T[] into)
*/
public Object[] toArray()
{
final Object[] arrayCopy = new Object[size];
final Object[] arrayCopy = new Object[size()];
copyValues(arrayCopy);

return arrayCopy;
}

Expand All @@ -517,6 +528,11 @@ private void copyValues(final Object[] arrayCopy)
{
arrayCopy[i] = iterator.next();
}

if (containsMissingValue)
{
arrayCopy[sizeOfArrayValues] = MISSING_VALUE;
}
}

/**
Expand All @@ -533,7 +549,9 @@ public boolean equals(final Object other)
{
final IntHashSet otherSet = (IntHashSet)other;

return otherSet.size == size && containsAll(otherSet);
return otherSet.containsMissingValue == containsMissingValue &&
otherSet.sizeOfArrayValues == sizeOfArrayValues &&
containsAll(otherSet);
}

return false;
Expand All @@ -553,31 +571,35 @@ public boolean equals(final Object other)
}
}

hashCode = 31 * hashCode + (containsMissingValue ? 1 : 0);

return hashCode;
}

public final class IntHashSetIterator extends IntIterator
{
IntHashSetIterator(final int[] values)
{
super(values);
}

public void remove()
{
if (isPositionValid)
{
@DoNotSub final int position = position();
values[position] = MISSING_VALUE;
--size;
--sizeOfArrayValues;

compactChain(position);

isPositionValid = false;
}
else
{
throw new IllegalStateException();
if (containsMissingValue)
{
containsMissingValue = false;
}
else
{
throw new IllegalStateException();
}
}
}
}
Expand Down
51 changes: 23 additions & 28 deletions src/main/java/org/agrona/collections/IntIterator.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
import java.util.Iterator;
import java.util.NoSuchElementException;

import static org.agrona.collections.IntHashSet.MISSING_VALUE;

/**
* An iterator for a sequence of primitive values.
*/
Expand All @@ -31,42 +29,28 @@ public class IntIterator implements Iterator<Integer>
@DoNotSub private int stopCounter;
protected boolean isPositionValid = false;
private int[] values;

/**
* Construct an {@link Iterator} over an array of primitives ints.
*
* @param values to iterate over.
*/
public IntIterator(final int[] values)
{
reset(values);
}

/**
* Reset methods for fixed size collections.
*/
void reset()
{
reset(values);
}
private boolean containsMissingValue;

/**
* Reset method for expandable collections.
*
* @param values to be iterated over
* @param containsMissingValue true iff the hashset contains a missing value
*/
void reset(final int[] values)
void reset(final int[] values, final boolean containsMissingValue)
{
this.values = values;
this.containsMissingValue = containsMissingValue;

@DoNotSub final int length = values.length;

@DoNotSub int i = length;
if (values[length - 1] != MISSING_VALUE)
if (values[length - 1] != IntHashSet.MISSING_VALUE)
{
i = 0;
for (@DoNotSub int size = length; i < size; i++)
{
if (values[i] == MISSING_VALUE)
if (values[i] == IntHashSet.MISSING_VALUE)
{
break;
}
Expand All @@ -91,13 +75,13 @@ public boolean hasNext()
for (@DoNotSub int i = positionCounter - 1; i >= stopCounter; i--)
{
@DoNotSub final int index = i & mask;
if (values[index] != MISSING_VALUE)
if (values[index] != IntHashSet.MISSING_VALUE)
{
return true;
}
}

return false;
return containsMissingValue;
}

protected void findNext()
Expand All @@ -109,15 +93,18 @@ protected void findNext()
for (@DoNotSub int i = positionCounter - 1; i >= stopCounter; i--)
{
@DoNotSub final int index = i & mask;
if (values[index] != MISSING_VALUE)
if (values[index] != IntHashSet.MISSING_VALUE)
{
positionCounter = i;
isPositionValid = true;
return;
}
}

throw new NoSuchElementException();
if (!containsMissingValue)
{
throw new NoSuchElementException();
}
}

public Integer next()
Expand All @@ -134,6 +121,14 @@ public int nextValue()
{
findNext();

return values[position()];
if (isPositionValid)
{
return values[position()];
}
else
{
containsMissingValue = false;
return IntHashSet.MISSING_VALUE;
}
}
}
Loading

0 comments on commit 5a5323d

Please sign in to comment.