All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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;
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy