software.amazon.event.ruler.IntIntMap Maven / Gradle / Ivy
package software.amazon.event.ruler;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* A fast primitive int-int map implementation. Keys and values may only be positive.
*/
final class IntIntMap implements Cloneable {
// taken from FastUtil
private static final int INT_PHI = 0x9E3779B9;
private static final long KEY_MASK = 0xFFFFFFFFL;
private static final long EMPTY_CELL = -1 & KEY_MASK;
public static final int NO_VALUE = -1;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* Capacity of 8, with data type long, translates to an initial {@link #table} of 64 bytes,
* which fits perfectly into the common cache line size.
*/
private static final int DEFAULT_INITIAL_CAPACITY = 8;
/**
* Holds key-value int pairs. The highest 32 bits hold the int value, and the lowest 32 bits
* hold the int key. Must always have a length that is a power of two so that {@link #mask} can
* be computed correctly.
*/
private long[] table;
/**
* Load factor, must be between (0 and 1)
*/
private final float loadFactor;
/**
* We will resize a map once it reaches this size
*/
private int threshold;
/**
* Current map size
*/
private int size;
/**
* Mask to calculate the position in the table for a key.
*/
private int mask;
IntIntMap() {
this(DEFAULT_INITIAL_CAPACITY);
}
IntIntMap(final int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
IntIntMap(final int initialCapacity, final float loadFactor) {
if (loadFactor <= 0 || loadFactor >= 1) {
throw new IllegalArgumentException("loadFactor must be in (0, 1)");
}
if (initialCapacity <= 0) {
throw new IllegalArgumentException("initialCapacity must be positive");
}
if (Integer.bitCount(initialCapacity) != 1) {
throw new IllegalArgumentException("initialCapacity must be a power of two");
}
this.mask = initialCapacity - 1;
this.loadFactor = loadFactor;
this.table = makeTable(initialCapacity);
this.threshold = (int) (initialCapacity * loadFactor);
}
/**
* Gets the value for {@code key}.
*
* @param key
* the non-negative key
* @return the value present at {@code key}, or {@link #NO_VALUE} if none is present.
*/
int get(final int key) {
int idx = getStartIndex(key);
do {
long cell = table[idx];
if (cell == EMPTY_CELL) {
// end of the chain, key does not exist
return NO_VALUE;
}
if (((int) (cell & KEY_MASK)) == key) {
// found the key
return (int) (cell >> 32);
}
// continue walking the chain
idx = getNextIndex(idx);
} while (true);
}
/**
* Puts {@code value} in {@code key}. {@code key} is restricted to positive integers to avoid an
* unresolvable collision with {@link #EMPTY_CELL}, while {@code value} is restricted to
* positive integers to avoid an unresolvable collision with {@link #NO_VALUE}.
*
* @param key
* the non-negative key
* @param value
* the non-negative value
* @return the value that was previously set for {@code key}, or {@link #NO_VALUE} if none was
* present.
* @throws IllegalArgumentException
* if {@code key} is negative
*/
int put(final int key, final int value) {
if (key < 0) {
throw new IllegalArgumentException("key cannot be negative");
}
if (value < 0) {
throw new IllegalArgumentException("value cannot be negative");
}
long cellToPut = (((long) key) & KEY_MASK) | (((long) value) << 32);
int idx = getStartIndex(key);
do {
long cell = table[idx];
if (cell == EMPTY_CELL) {
// found an empty cell
table[idx] = cellToPut;
if (size >= threshold) {
rehash(table.length * 2);
// 'size' is set inside rehash()
} else {
size++;
}
return NO_VALUE;
}
if (((int) (cell & KEY_MASK)) == key) {
// found a non-empty cell with a key matching the one we're writing, so overwrite it
table[idx] = cellToPut;
return (int) (cell >> 32);
}
// continue walking the chain
idx = getNextIndex(idx);
} while (true);
}
/**
* Removes {@code key}.
*
* @param key
* the non-negative key
* @return the removed value, or {@link #NO_VALUE} if none was present.
* @throws IllegalArgumentException
* if {@code key} is negative
*/
int remove(final int key) {
int idx = getStartIndex(key);
do {
long cell = table[idx];
if (cell == EMPTY_CELL) {
// end of the chain, key does not exist
return NO_VALUE;
}
if (((int) (cell & KEY_MASK)) == key) {
// found the key
size--;
shiftKeys(idx);
return (int) (cell >> 32);
}
// continue walking the chain
idx = getNextIndex(idx);
} while (true);
}
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map
*/
int size() {
return size;
}
boolean isEmpty() {
return size == 0;
}
public Iterable entries() {
return new Iterable () {
@Override
public Iterator iterator() {
return new EntryIterator();
}
};
}
@Override
public Object clone() {
IntIntMap result;
try {
result = (IntIntMap) super.clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
result.table = table.clone();
return result;
}
/**
* Shifts entries with the same hash.
*/
private void shiftKeys(final int index) {
int last;
int pos = index;
while (true) {
last = pos;
do {
pos = (pos + 1) & mask;
if (table[pos] == EMPTY_CELL) {
table[last] = EMPTY_CELL;
return;
}
int key = (int) (table[pos] & KEY_MASK);
int keyStartIndex = getStartIndex(key);
if (last < pos) { // did we wrap around?
/*
* (no) if the previous position is after the chain startIndex for key, *or* the
* chain startIndex of the key is after the position we're checking, then the
* position we're checking now cannot be a part of the current chain
*/
if (last >= keyStartIndex || keyStartIndex > pos) {
break;
}
} else {
/*
* (yes) if the previous position is after the chain startIndex for key, *and*
* the chain startIndex of key is after the position we're checking, then the
* position we're checking now cannot be a part of the current chain
*/
if (last >= keyStartIndex && keyStartIndex > pos) {
break;
}
}
} while (true);
table[last] = table[pos];
}
}
private void rehash(final int newCapacity) {
threshold = (int) (newCapacity * loadFactor);
mask = newCapacity - 1;
final int oldCapacity = table.length;
final long[] oldTable = table;
table = makeTable(newCapacity);
size = 0;
for (int i = oldCapacity - 1; i >= 0; i--) {
if (oldTable[i] != EMPTY_CELL) {
final int oldKey = (int) (oldTable[i] & KEY_MASK);
final int oldValue = (int) (oldTable[i] >> 32);
put(oldKey, oldValue);
}
}
}
private static long[] makeTable(final int capacity) {
long[] result = new long[capacity];
Arrays.fill(result, EMPTY_CELL);
return result;
}
private int getStartIndex(final int key) {
return phiMix(key) & mask;
}
private int getNextIndex(final int currentIndex) {
return (currentIndex + 1) & mask;
}
/**
* Computes hashcode for {@code val}.
*
* @param val
* @return the hashcode for {@code val}
*/
private static int phiMix(final int val) {
final int h = val * INT_PHI;
return h ^ (h >> 16);
}
static class Entry {
private final int key;
private final int value;
private Entry(final int key, final int value) {
this.key = key;
this.value = value;
}
public int getKey() {
return key;
}
public int getValue() {
return value;
}
}
private class EntryIterator implements Iterator {
private static final int NO_NEXT_INDEX = -1;
private int nextIndex = findNextIndex(0);
@Override
public boolean hasNext() {
return nextIndex != NO_NEXT_INDEX;
}
@Override
public Entry next() {
if (nextIndex == NO_NEXT_INDEX) {
throw new NoSuchElementException();
}
Entry entry = new Entry((int) (table[nextIndex] & KEY_MASK), (int) (table[nextIndex] >> 32));
nextIndex = findNextIndex(nextIndex + 1);
return entry;
}
private int findNextIndex(int fromIndex) {
while (fromIndex < table.length) {
if (table[fromIndex] != EMPTY_CELL) {
return fromIndex;
}
fromIndex++;
}
return NO_NEXT_INDEX;
}
}
}