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

com.yahoo.collections.Hashlet Maven / Gradle / Ivy

// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.collections;


/**
 * Lightweight hash map from key to value with limited
 * functionality. This class lets you build a map from key to
 * value. The value for a key may be overwritten and the put and get
 * methods have the same semantics as for normal Java Maps, but there
 * is no remove operation. Also, there is no iterator support, but
 * keys and values can be accessed directly by index. The access order
 * of keys and values are defined by the insert order of the keys. The
 * goal of this class is to reduce the amount of object that are
 * allocated by packing everything into two internal arrays. The keys
 * and values are packed in an Object array and the hash table and
 * entries are packed in an int array. The internal arrays are not
 * created until space is needed. The default initial capacity is 16
 * entries. If you know you need much more space than this, you can
 * explicitly reserve more space before starting to insert values. The
 * maximum load factor is 0.7 and drops slightly with increasing
 * capacity.
 *
 * @author Havard Pettersen
 */
public final class Hashlet {

    private static final int[] emptyHash = new int[1];
    private int capacity = 0;
    private int hashSize() { return (capacity + (capacity / 2) - 1); }
    private int used = 0;
    private Object[] store;
    private int[] hash = emptyHash;

    /**
     * Create an empty Hashlet.
     **/
    public Hashlet() {}

    /**
     * Create a Hashlet that is a shallow copy of another Hashlet.
     *
     * @param hashlet the Hashlet to copy.
     **/
    public Hashlet(Hashlet hashlet) {
        if (hashlet.used > 0) {
            capacity = hashlet.capacity;
            used = hashlet.used;
            store = new Object[hashlet.store.length];
            hash = new int[hashlet.hash.length];
            System.arraycopy(hashlet.store, 0, store, 0, store.length);
            System.arraycopy(hashlet.hash, 0, hash, 0, hash.length);
        }
    }

    /**
     * Reserve space for more key value pairs. This method is used by
     * the put method to perform rehashing when needed. It can be
     * invoked directly by the application to reduce the number of
     * rehashes needed to insert a large number of entries.
     *
     * @param n the number of additional entries to reserve space for
     **/
    public void reserve(int n) {
        if (used + n > capacity) {
            final int c = capacity;
            if (capacity == 0) {
                capacity = 16;
            }
            while (used + n > capacity) {
                capacity *= 2;
            }
            final Object[] s = store;
            store = new Object[capacity * 2];
            hash = new int[hashSize() + (capacity * 2)];
            if (c > 0) {
                System.arraycopy(s, 0, store, 0, used);
                System.arraycopy(s, c, store, capacity, used);
                for (int i = 0; i < used; i++) {
                    int prev = Math.abs(s[i].hashCode() % hashSize());
                    int entry = hash[prev];
                    while (entry != 0) {
                        prev = entry + 1;
                        entry = hash[prev];
                    }
                    final int insertIdx = (hashSize() + (i * 2));
                    hash[prev] = insertIdx;
                    hash[insertIdx] = i;
                }
            }
        }
    }

    /**
     * The current size. This is the number of key value pairs
     * currently stored in this object.
     *
     * @return current size
     **/
    public int size() {
        return used;
    }

    /**
     * Obtain a key. Keys are accessed in the order they were first
     * inserted.
     *
     * @return the requested key
     * @param i the index of the key, must be in the range [0, size() - 1]
     **/
    @SuppressWarnings("unchecked")
    public K key(int i) {
        return (K) store[i];
    }

    /**
     * Obtain a value. Values are accessed in the order in which
     * theirs keys were first inserted.
     *
     * @return the requested value
     * @param i the index of the value, must be in the range [0, size() - 1]
     **/
    @SuppressWarnings("unchecked")
    public V value(int i) {
        return (V) store[capacity + i];
    }

    /**
     * This will replace the value at the index give.
     *
     * @param i the index of the value, must be in the range [0, size() - 1]
     * @param value The new value you want to set for this index.
     * @return previous value
     */
    public V setValue(int i, V value) {
        V prev = value(i);
        store[capacity + i] = value;
        return prev;
    }

    /**
     * Associate a value with a specific key.
     *
     * @return the old value for the key, if it was already present
     * @param key the key
     * @param value the value
     **/
    public V put(K key, V value) {
        reserve(1);
        int prev = Math.abs(key.hashCode() % hashSize());
        int entry = hash[prev];
        while (entry != 0) {
            final int idx = hash[entry];
            if (store[idx].equals(key)) { // found entry
                @SuppressWarnings("unchecked")
                final V ret = (V) store[capacity + idx];
                store[capacity + idx] = value;
                return ret;
            }
            prev = entry + 1;
            entry = hash[prev];
        }
        final int insertIdx = (hashSize() + (used * 2));
        hash[prev] = insertIdx;
        hash[insertIdx] = used;
        store[used] = key;
        store[capacity + (used++)] = value;
        return null;
    }

    /**
     * Obtain the value for a specific key.
     *
     * @return the value for a key, or null if not found
     * @param key the key
     **/
    public V get(Object key) {
        int index = getIndexOfKey(key);
        return (index != -1) ? value(index) : null;
    }

    /**
     * Finds the index where the key,value pair is stored.
     * @param key to look for
     * @return the index where the key is found or -1 if it is not found
     */
    public int getIndexOfKey(Object key) {
        int entry = hash[Math.abs(key.hashCode() % hashSize())];
        while (entry != 0) {
            final int idx = hash[entry];
            if (store[idx].equals(key)) { // found entry
                return idx;
            }
            entry = hash[entry + 1];
        }
        return -1;
    }

    @Override
    public int hashCode() {
        int h = 0;
        for (int i = 0; i < used; i++) {
            h += key(i).hashCode();
            V v = value(i);
            if (v != null) {
                h += v.hashCode();
            }
        }
        return h;
    }

    @Override
    public boolean equals(Object o) {
        if (! (o instanceof Hashlet) ) return false;
        Hashlet rhs = (Hashlet) o;
        if (used != rhs.used) return false;
        for (int i = 0; i < used; i++) {
            int bi = rhs.getIndexOfKey(key(i));
            if (bi == -1) return false;
            Object a = value(i);
            Object b = rhs.value(bi);
            boolean equal = (a == null) ? b == null : a.equals(b);
            if ( !equal ) return false;
        }
        return true;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy