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

org.agrona.collections.Int2IntHashMap Maven / Gradle / Ivy

There is a newer version: 0.9.1
Show newest version
/*
 *  Copyright 2014 - 2016 Real Logic Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.agrona.collections;

import org.agrona.BitUtil;
import org.agrona.generation.DoNotSub;

import java.util.*;
import java.util.function.BiConsumer;

import static org.agrona.collections.CollectionUtil.validateLoadFactor;

/**
 * A open addressing with linear probing hash map specialised for primitive key and value pairs.
 */
public class Int2IntHashMap implements Map
{
    @DoNotSub private final float loadFactor;
    private final int missingValue;
    @DoNotSub private int resizeThreshold;
    @DoNotSub private int size = 0;

    private int[] entries;
    private final KeySet keySet;
    private final Values values;
    private final Set> entrySet;

    public Int2IntHashMap(final int missingValue)
    {
        this(16, 0.67f, missingValue);
    }

    @SuppressWarnings("unchecked")
    public Int2IntHashMap(
        @DoNotSub final int initialCapacity,
        @DoNotSub final float loadFactor,
        final int missingValue)
    {
        validateLoadFactor(loadFactor);

        this.loadFactor = loadFactor;
        this.missingValue = missingValue;

        capacity(BitUtil.findNextPositivePowerOfTwo(initialCapacity));

        keySet = new KeySet();
        values = new Values();
        entrySet = new EntrySet();
    }

    /**
     * The value to be used as a null marker in the map.
     *
     * @return value to be used as a null marker in the map.
     */
    public int missingValue()
    {
        return missingValue;
    }

    /**
     * Get the load factor applied for resize operations.
     *
     * @return the load factor applied for resize operations.
     */
    public float loadFactor()
    {
        return loadFactor;
    }

    /**
     * {@inheritDoc}
     */
    @DoNotSub public int size()
    {
        return size;
    }

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

    public int get(final int key)
    {
        final int[] entries = this.entries;
        final int missingValue = this.missingValue;
        @DoNotSub final int mask = entries.length - 1;
        @DoNotSub int index = Hashing.evenHash(key, mask);

        int value = missingValue;
        int candidateKey;
        while ((candidateKey = entries[index]) != missingValue)
        {
            if (candidateKey == key)
            {
                value = entries[index + 1];
                break;
            }

            index = next(index, mask);
        }

        return value;
    }

    public int put(final int key, final int value)
    {
        final int[] entries = this.entries;
        final int missingValue = this.missingValue;
        @DoNotSub final int mask = entries.length - 1;
        @DoNotSub int index = Hashing.evenHash(key, mask);
        int oldValue = missingValue;

        int candidateKey;
        while ((candidateKey = entries[index]) != missingValue)
        {
            if (candidateKey == key)
            {
                oldValue = entries[index + 1];
                break;
            }

            index = next(index, mask);
        }

        if (oldValue == missingValue)
        {
            ++size;
            entries[index] = key;
        }

        entries[index + 1] = value;

        increaseCapacity();

        return oldValue;
    }

    private void increaseCapacity()
    {
        if (size > resizeThreshold)
        {
            // entries.length = 2 * capacity
            @DoNotSub final int newCapacity = entries.length;
            rehash(newCapacity);
        }
    }

    private void rehash(@DoNotSub final int newCapacity)
    {
        final int[] oldEntries = entries;
        final int missingValue = this.missingValue;
        @DoNotSub final int length = entries.length;

        capacity(newCapacity);

        for (@DoNotSub int i = 0; i < length; i += 2)
        {
            final int key = oldEntries[i];
            if (key != missingValue)
            {
                put(key, oldEntries[i + 1]);
            }
        }
    }

    /**
     * Primitive specialised forEach implementation.
     * 

* NB: Renamed from forEach to avoid overloading on parameter types of lambda * expression, which doesn't interplay well with type inference in lambda expressions. * * @param consumer a callback called for each key/value pair in the map. */ public void intForEach(final IntIntConsumer consumer) { final int[] entries = this.entries; final int missingValue = this.missingValue; @DoNotSub final int length = entries.length; for (@DoNotSub int i = 0; i < length; i += 2) { final int key = entries[i]; if (key != missingValue) { consumer.accept(entries[i], entries[i + 1]); } } } /** * Int primitive specialised containsKey. * * @param key the key to check. * @return true if the map contains key as a key, false otherwise. */ public boolean containsKey(final int key) { return get(key) != missingValue; } public boolean containsValue(final int value) { final int[] entries = this.entries; @DoNotSub final int length = entries.length; boolean found = false; for (@DoNotSub int i = 1; i < length; i += 2) { if (value == entries[i]) { found = true; break; } } return found; } /** * {@inheritDoc} */ public void clear() { Arrays.fill(entries, missingValue); size = 0; } /** * Compact the backing arrays by rehashing with a capacity just larger than current size * and giving consideration to the load factor. */ public void compact() { @DoNotSub final int idealCapacity = (int)Math.round(size() * (1.0d / loadFactor)); rehash(BitUtil.findNextPositivePowerOfTwo(idealCapacity)); } // ---------------- Boxed Versions Below ---------------- /** * {@inheritDoc} */ public Integer get(final Object key) { return get((int)key); } /** * {@inheritDoc} */ public Integer put(final Integer key, final Integer value) { return put((int)key, (int)value); } /** * {@inheritDoc} */ public void forEach(final BiConsumer action) { intForEach(action::accept); } /** * {@inheritDoc} */ public boolean containsKey(final Object key) { return containsKey((int)key); } /** * {@inheritDoc} */ public boolean containsValue(final Object value) { return containsValue((int)value); } /** * {@inheritDoc} */ public void putAll(final Map map) { for (final Map.Entry entry : map.entrySet()) { put(entry.getKey(), entry.getValue()); } } /** * {@inheritDoc} */ public KeySet keySet() { return keySet; } /** * {@inheritDoc} */ public Values values() { return values; } /** * {@inheritDoc} */ public Set> entrySet() { return entrySet; } /** * {@inheritDoc} */ public Integer remove(final Object key) { return remove((int)key); } public int remove(final int key) { final int[] entries = this.entries; final int missingValue = this.missingValue; @DoNotSub final int mask = entries.length - 1; @DoNotSub int index = Hashing.evenHash(key, mask); int oldValue = missingValue; int candidateKey; while ((candidateKey = entries[index]) != missingValue) { if (candidateKey == key) { @DoNotSub final int valueIndex = index + 1; oldValue = entries[valueIndex]; entries[index] = missingValue; entries[valueIndex] = missingValue; size--; compactChain(index); break; } index = next(index, mask); } return oldValue; } private void compactChain(@DoNotSub int deleteIndex) { final int[] entries = this.entries; final int missingValue = this.missingValue; @DoNotSub final int mask = entries.length - 1; @DoNotSub int index = deleteIndex; while (true) { index = next(index, mask); if (entries[index] == missingValue) { break; } @DoNotSub final int hash = Hashing.evenHash(entries[index], mask); if ((index < hash && (hash <= deleteIndex || deleteIndex <= index)) || (hash <= deleteIndex && deleteIndex <= index)) { entries[deleteIndex] = entries[index]; entries[deleteIndex + 1] = entries[index + 1]; entries[index] = missingValue; entries[index + 1] = missingValue; deleteIndex = index; } } } /** * Get the minimum value stored in the map. If the map is empty then it will return {@link #missingValue()} * * @return the minimum value stored in the map. */ public int minValue() { final int missingValue = this.missingValue; int min = size == 0 ? missingValue : Integer.MAX_VALUE; final int[] entries = this.entries; @DoNotSub final int length = entries.length; for (@DoNotSub int i = 1; i < length; i += 2) { final int value = entries[i]; if (value != missingValue) { min = Math.min(min, value); } } return min; } /** * Get the maximum value stored in the map. If the map is empty then it will return {@link #missingValue()} * * @return the maximum value stored in the map. */ public int maxValue() { final int missingValue = this.missingValue; int max = size == 0 ? missingValue : Integer.MIN_VALUE; final int[] entries = this.entries; @DoNotSub final int length = entries.length; for (@DoNotSub int i = 1; i < length; i += 2) { final int value = entries[i]; if (value != missingValue) { max = Math.max(max, value); } } return max; } /** * {@inheritDoc} */ public String toString() { final StringBuilder sb = new StringBuilder(); sb.append('{'); for (final Entry entry : entrySet()) { sb.append(entry.getKey().intValue()); sb.append('='); sb.append(entry.getValue().intValue()); sb.append(", "); } if (sb.length() > 1) { sb.setLength(sb.length() - 2); } sb.append('}'); return sb.toString(); } /** * Primitive specialised version of {@link #replace(Object, Object)} * * @param key key with which the specified value is associated * @param value value to be associated with the specified key * @return the previous value associated with the specified key, or * {@link #missingValue()} if there was no mapping for the key. */ public int replace(final int key, final int value) { int curValue = get(key); if (curValue != missingValue) { curValue = put(key, value); } return curValue; } /** * Primitive specialised version of {@link #replace(Object, Object, Object)} * * @param key key with which the specified value is associated * @param oldValue value expected to be associated with the specified key * @param newValue value to be associated with the specified key * @return {@code true} if the value was replaced */ public boolean replace(final int key, int oldValue, int newValue) { final int curValue = get(key); if (curValue != oldValue || curValue == missingValue) { return false; } put(key, newValue); return true; } @DoNotSub private static int next(final int index, final int mask) { return (index + 2) & mask; } private void capacity(@DoNotSub int newCapacity) { @DoNotSub final int entriesLength = newCapacity * 2; if (entriesLength < 0) { throw new IllegalStateException("Max capacity reached at size=" + size); } /*@DoNotSub*/ resizeThreshold = (int)(newCapacity * loadFactor); entries = new int[entriesLength]; size = 0; Arrays.fill(entries, missingValue); } // ---------------- Utility Classes ---------------- abstract class AbstractIterator { @DoNotSub private int positionCounter; @DoNotSub private int stopCounter; AbstractIterator() { reset(); } private void reset() { final int missingValue = Int2IntHashMap.this.missingValue; final int[] entries = Int2IntHashMap.this.entries; @DoNotSub final int capacity = entries.length; @DoNotSub int i = capacity; if (entries[capacity - 2] != missingValue) { i = 0; for (@DoNotSub int size = capacity; i < size; i += 2) { if (entries[i] == missingValue) { break; } } } stopCounter = i; positionCounter = i + capacity; } @DoNotSub protected int keyPosition() { return positionCounter & entries.length - 1; } public boolean hasNext() { final int[] entries = Int2IntHashMap.this.entries; final int missingValue = Int2IntHashMap.this.missingValue; @DoNotSub final int mask = entries.length - 1; boolean hasNext = false; for (@DoNotSub int i = positionCounter - 2; i >= stopCounter; i -= 2) { @DoNotSub final int index = i & mask; if (entries[index] != missingValue) { hasNext = true; break; } } return hasNext; } protected void findNext() { final int[] entries = Int2IntHashMap.this.entries; final int missingValue = Int2IntHashMap.this.missingValue; @DoNotSub final int mask = entries.length - 1; for (@DoNotSub int i = positionCounter - 2; i >= stopCounter; i -= 2) { @DoNotSub final int index = i & mask; if (entries[index] != missingValue) { positionCounter = i; return; } } throw new NoSuchElementException(); } } public final class IntIterator extends AbstractIterator implements Iterator { @DoNotSub private final int offset; private IntIterator( @DoNotSub final int offset) { this.offset = offset; } public Integer next() { return nextValue(); } public int nextValue() { findNext(); return entries[keyPosition() + offset]; } public IntIterator reset() { super.reset(); return this; } } final class EntryIterator extends AbstractIterator implements Iterator>, Entry { private int key; private int value; private EntryIterator() { super(); } public Integer getKey() { return key; } public Integer getValue() { return value; } public Integer setValue(final Integer value) { throw new UnsupportedOperationException(); } public Entry next() { findNext(); @DoNotSub final int keyPosition = keyPosition(); key = entries[keyPosition]; value = entries[keyPosition + 1]; return this; } public EntryIterator reset() { super.reset(); key = missingValue; value = missingValue; return this; } } public final class KeySet extends MapDelegatingSet { private final IntIterator keyIterator = new IntIterator(0); private KeySet() { super(Int2IntHashMap.this); } /** * {@inheritDoc} */ public IntIterator iterator() { return keyIterator.reset(); } /** * {@inheritDoc} */ public boolean contains(final Object o) { return contains((int)o); } public boolean contains(final int key) { return containsKey(key); } } public final class Values extends AbstractCollection { private final IntIterator valueIterator = new IntIterator(1); private Values() { } /** * {@inheritDoc} */ public IntIterator iterator() { return valueIterator.reset(); } /** * {@inheritDoc} */ @DoNotSub public int size() { return Int2IntHashMap.this.size(); } /** * {@inheritDoc} */ public boolean contains(final Object o) { return contains((int)o); } public boolean contains(final int key) { return containsValue(key); } } private final class EntrySet extends MapDelegatingSet> { private final EntryIterator entryIterator = new EntryIterator(); private EntrySet() { super(Int2IntHashMap.this); } /** * {@inheritDoc} */ public Iterator> iterator() { return entryIterator.reset(); } /** * {@inheritDoc} */ public boolean contains(final Object o) { return containsKey(((Entry)o).getKey()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy