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

com.ajjpj.abase.collection.immutable.AHashMap Maven / Gradle / Ivy

Go to download

a-base is a library of basic (hence the name) classes, most notably immutable collection classes with copy-on-write operations

The newest version!
package com.ajjpj.abase.collection.immutable;

import com.ajjpj.abase.collection.ACompositeIterator;
import com.ajjpj.abase.collection.AEquality;
import com.ajjpj.abase.collection.APair;
import com.ajjpj.abase.function.AFunction1;

import java.io.Serializable;
import java.util.*;


/**
 * This is an immutable hash map based on 32-way hash tries. Its implementation is optimized to minimize copying when
 *  the map is modified.

* * The code in this class is essentially a port of the HashMap class from the Scala standard library. Thank you for * the excellent code, Scala team! * * @author arno */ public class AHashMap implements AMap, Serializable { private static final int LEVEL_INCREMENT = 5; private static final AEquality DEFAULT_EQUALITY = AEquality.EQUALS; private static final AHashMap emptyEquals = new AHashMap<>(AEquality.EQUALS); private static final AHashMap emptyIdentity = new AHashMap<>(AEquality.IDENTITY); final AEquality equality; transient private Integer cachedHashcode = null; // intentionally not volatile: This class is immutable, so recalculating per thread works /** * Returns an empty AHashMap instance with default (i.e. equals-based) equalityForEquals. Calling this factory method instead * of the constructor allows internal reuse of empty map instances since they are immutable. */ public static AHashMap empty() { return empty(DEFAULT_EQUALITY); } /** * Returns an empty AHashMap instance with the given equalityForEquals strategy. Calling this factory method instead of * the constructor allows internal reuse of empty map instances since they are immutable. */ @SuppressWarnings("unchecked") public static AHashMap empty(AEquality equality) { // for typical equalityForEquals implementations, return pre-instantiated objects if(equality == AEquality.EQUALS) return (AHashMap) emptyEquals; if(equality == AEquality.IDENTITY) return (AHashMap) emptyIdentity; return new AHashMap<>(equality); } /** * Returns an AHashMap instance with default (i.e. equals-based) equalityForEquals, initializing it from the contents of * a given java.util.Map. */ @SuppressWarnings("unused") public static AHashMap fromJavaUtilMap(Map map) { return fromJavaUtilMap(DEFAULT_EQUALITY, map); } /** * Returns an AHashMap instance for a given equalityForEquals, initializing it from the contents of a given * java.util.Map. */ public static AHashMap fromJavaUtilMap(AEquality equality, Map map) { AHashMap result = new AHashMap<>(equality); for(Map.Entry entry: map.entrySet()) { result = result.updated(entry.getKey(), entry.getValue()); } return result; } /** * Returns an AHashMap instance with default (i.e. equals-based) equalityForEquals, initializing it from separate 'keys' * and 'values' collections. Both collections are iterated exactly once, and are expected to have the same size. */ public static AHashMap fromKeysAndValues(Iterable keys, Iterable values) { return fromKeysAndValues(DEFAULT_EQUALITY, keys, values); } /** * Returns an AHashMap instance with a given equalityForEquals, initializing it from separate 'keys' * and 'values' collections. Both collections are iterated exactly once, and are expected to have the same size. */ public static AHashMap fromKeysAndValues(AEquality equality, Iterable keys, Iterable values) { final Iterator ki = keys.iterator(); final Iterator vi = values.iterator(); AHashMap result = AHashMap.empty(equality); while(ki.hasNext()) { final K key = ki.next(); final V value = vi.next(); result = result.updated(key, value); } return result; } /** * Returns an AHashMap instance with default (i.e. equals-based) equalityForEquals, initializing it from a collection of * keys and a function. For each element of the keys collection, the function is called once to * determine the corresponding value, and the pair is then stored in the map. */ @SuppressWarnings("unused") public static AHashMap fromKeysAndFunction(Iterable keys, AFunction1 f) throws E { return fromKeysAndFunction(DEFAULT_EQUALITY, keys, f); } /** * Returns an AHashMap instance with a given equalityForEquals, initializing it from a collection of * keys and a function. For each element of the keys collection, the function is called once to * determine the corresponding value, and the pair is then stored in the map. */ public static AHashMap fromKeysAndFunction(AEquality equality, Iterable keys, AFunction1 f) throws E { final Iterator ki = keys.iterator(); AHashMap result = AHashMap.empty(equality); while(ki.hasNext()) { final K key = ki.next(); final V value = f.apply(key); result = result.updated(key, value); } return result; } private AHashMap(AEquality equality) { this.equality = equality; } @Override public int size() { return 0; } @Override public boolean isEmpty() { return size() == 0; } @Override public boolean nonEmpty() { return size() > 0; } @Override public boolean containsKey(K key) { return get(key).isDefined(); } @Override public boolean containsValue(V value) { for(V cur: values()) { if(equality.equals(value, cur)) { return true; } } return false; } @Override public AOption get(K key) { return doGet(key, computeHash(key, equality), 0); } @Override public V getRequired(K key) { return get(key).get(); } @Override public AHashMap updated(K key, V value) { return doUpdated(key, computeHash(key, equality), 0, value); } @Override public AHashMap removed(K key) { return doRemoved(key, computeHash(key, equality), 0); } @Override public AMap withDefaultValue(V defaultValue) { return new AMapWithDefaultValue<>(this, defaultValue); } @Override public AMap withDefault(AFunction1 function) { return new AMapWithDefault<>(this, function); } @SuppressWarnings("unchecked") @Override public boolean equals(Object o) { if(o == this) { return true; } if(! (o instanceof AMap)) { return false; } final AMap other = (AMap) o; if(size() != other.size()) { return false; } for(APair el: this) { final AOption otherValue = other.get(el._1); if(otherValue.isEmpty()) { return false; } if(! equality.equals(el._2, otherValue.get())) { return false; } } return true; } @Override public int hashCode() { if(cachedHashcode == null) { int result = 0; for(APair el: this) { result = result ^ (31*equality.hashCode(el._1) + equality.hashCode(el._2)); } cachedHashcode = result; } return cachedHashcode; } @Override public Iterator> iterator() { return new Iterator>() { @Override public boolean hasNext() { return false; } @Override public APair next() { throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public Set keys() { return Collections.emptySet(); } @Override public Collection values() { return Collections.emptyList(); } @Override public Map asJavaUtilMap() { return new JavaUtilMapWrapper<>(this); } /** * @param level number of least significant bits of the hash to discard for local hash lookup. This mechanism * is used to create a 32-way hash trie - level increases by 5 at each level */ AOption doGet(K key, int hash, int level) { return AOption.none(); } AHashMap doUpdated(K key, int hash, int level, V value) { return new HashMap1<> (key, hash, value, equality); } AHashMap doRemoved(K key, int hash, int level) { return this; } private static int computeHash(Object key, AEquality equality) { //TODO ask why this algorithm is used int h = equality.hashCode(key); h = h + ~(h << 9); h = h ^ (h >>> 14); h = h + (h << 4); return h ^ (h >>> 10); } @SuppressWarnings("unchecked") private static AHashMap[] createArray(int size) { return new AHashMap[size]; } @Override public String toString() { final StringBuilder result = new StringBuilder("{"); boolean first = true; for(APair e: this) { if(first) { first = false; } else { result.append(", "); } result.append(e._1).append("->").append(e._2); } result.append("}"); return result.toString(); } /** * very internal method. It assumes hash0 != hash1. */ private static HashTrieMap mergeLeafMaps(int hash0, AHashMap elem0, int hash1, AHashMap elem1, int level, int size, AEquality equality) { final int index0 = (hash0 >>> level) & 0x1f; final int index1 = (hash1 >>> level) & 0x1f; if(index0 != index1) { final int bitmap = (1 << index0) | (1 << index1); final AHashMap[] elems = createArray(2); if(index0 < index1) { elems[0] = elem0; elems[1] = elem1; } else { elems[0] = elem1; elems[1] = elem0; } return new HashTrieMap<>(bitmap, elems, size, equality); } else { final AHashMap[] elems = createArray(1); final int bitmap = (1 << index0); // try again, based on the elems[0] = mergeLeafMaps(hash0, elem0, hash1, elem1, level + LEVEL_INCREMENT, size, equality); return new HashTrieMap<>(bitmap, elems, size, equality); } } static class HashMap1 extends AHashMap { private final K key; private final int hash; private final V value; HashMap1(K key, int hash, V value, AEquality equality) { super(equality); this.key = key; this.hash = hash; this.value = value; } @Override public int size() { return 1; } @Override AOption doGet(K key, int hash, int level) { if(equality.equals(this.key, key)) { return AOption.some(value); } return AOption.none(); } @Override AHashMap doUpdated(K key, int hash, int level, V value) { if (hash == this.hash && equality.equals(key, this.key)) { if(this.value == value) { return this; } else { return new HashMap1<>(key, hash, value, equality); } } else { if (hash != this.hash) { // they have different hashes, but may collide at this level - find a level at which they don't final AHashMap that = new HashMap1<>(key, hash, value, equality); return mergeLeafMaps(this.hash, this, hash, that, level, 2, equality); } else { // hash collision --> store all elements in the same bin return new HashMapCollision1<> (hash, AListMap.empty(equality).updated(this.key,this.value).updated(key,value)); } } } @Override AHashMap doRemoved(K key, int hash, int level) { if (hash == this.hash && equality.equals(key, this.key)) { return empty(equality); } else { return this; } } @Override public Iterator> iterator() { return new Iterator>() { boolean initial = true; @Override public boolean hasNext() { return initial; } @Override public APair next() { if(initial) { initial = false; return new APair<> (key, value); } throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public Set keys() { return Collections.singleton(key); } @Override public Collection values() { return Collections.singletonList(value); } } static class HashMapCollision1 extends AHashMap { private final int hash; private final AListMap kvs; HashMapCollision1(int hash, AListMap kvs) { super(kvs.equality); this.hash = hash; this.kvs = kvs; } @Override public int size() { return kvs.size(); } @Override AOption doGet(K key, int hash, int level) { if (hash == this.hash) { return kvs.get(key); } else { return AOption.none(); } } @Override AHashMap doUpdated(K key, int hash, int level, V value) { if (hash == this.hash) { return new HashMapCollision1<>(hash, kvs.updated(key, value)); } else { final HashMap1 that = new HashMap1<>(key, hash, value, equality); return mergeLeafMaps(this.hash, this, hash, that, level, size() + 1, equality); } } @Override AHashMap doRemoved(K key, int hash, int level) { if (hash == this.hash) { final AListMap kvs1 = kvs.removed(key); if (kvs1.isEmpty()) { return AHashMap.empty(equality); } else if(kvs1.tail().isEmpty()) { return new HashMap1<>(kvs1.key(), computeHash(kvs1.key(), equality), kvs1.value(), equality); } else { return new HashMapCollision1<>(hash, kvs1); } } else { return this; } } @Override public Iterator> iterator() { return kvs.iterator(); } @Override public Set keys() { return kvs.keys(); } @Override public Collection values() { return kvs.values(); } } static class HashTrieMap extends AHashMap { final int bitmap; final AHashMap[] elems; final int size; HashTrieMap(int bitmap, AHashMap[] elems, int size, AEquality equality) { super(equality); this.bitmap = bitmap; this.elems = elems; this.size = size; } @Override public int size() { return size; } @Override public Iterator> iterator() { final List>> innerIter = new ArrayList<>(elems.length); for(AHashMap m: elems) { innerIter.add(m.iterator()); } return new ACompositeIterator<>(innerIter); } @Override public Set keys() { return new KeySet(); } @Override public Collection values() { return new ValueCollection(); } @SuppressWarnings({"NullableProblems", "unchecked", "SuspiciousToArrayCall"}) class KeySet implements Set { @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public boolean contains(Object o) { return containsKey((K) o); } @Override public Iterator iterator() { final List> innerIter = new ArrayList<>(elems.length); for(AHashMap m: elems) { innerIter.add(m.keys().iterator()); } return new ACompositeIterator<>(innerIter); } @Override public Object[] toArray() { return new ArrayList<>(this).toArray(); } @Override public T[] toArray(T[] a) { return new ArrayList<>(this).toArray(a); } @Override public boolean add(K k) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection c) { for(Object o: c) { if(!contains(o)) { return false; } } return true; } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } } @SuppressWarnings({"NullableProblems", "unchecked", "SuspiciousToArrayCall"}) class ValueCollection implements Collection { @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public boolean contains(Object o) { return containsValue((V) o); } @Override public Iterator iterator() { final List> innerIter = new ArrayList<>(elems.length); for(AHashMap m: elems) { innerIter.add(m.values().iterator()); } return new ACompositeIterator<>(innerIter); } @Override public Object[] toArray() { return new ArrayList<>(this).toArray(); } @Override public T[] toArray(T[] a) { return new ArrayList<>(this).toArray(a); } @Override public boolean add(V v) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection c) { for(Object o: c) { if(!contains(o)) { return false; } } return true; } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } } @Override AOption doGet(K key, int hash, int level) { final int index = (hash >>> level) & 0x1f; final int mask = 1 << index; if (bitmap == - 1) { return elems[index & 0x1f].doGet(key, hash, level + LEVEL_INCREMENT); } else if ((bitmap & mask) != 0) { final int offset = Integer.bitCount(bitmap & (mask - 1)); return elems[offset].doGet(key, hash, level + LEVEL_INCREMENT); } else { return AOption.none(); } } @Override AHashMap doUpdated(K key, int hash, int level, V value) { final int index = (hash >>> level) & 0x1f; final int mask = (1 << index); final int offset = Integer.bitCount(bitmap & (mask - 1)); if ((bitmap & mask) != 0) { final AHashMap sub = elems[offset]; final AHashMap subNew = sub.doUpdated(key, hash, level + LEVEL_INCREMENT, value); if(subNew == sub) { return this; } else { final AHashMap[] elemsNew = createArray(elems.length); System.arraycopy(elems, 0, elemsNew, 0, elems.length); elemsNew[offset] = subNew; return new HashTrieMap<> (bitmap, elemsNew, size + (subNew.size() - sub.size()), equality); } } else { final AHashMap[] elemsNew = createArray(elems.length + 1); System.arraycopy(elems, 0, elemsNew, 0, offset); elemsNew[offset] = new HashMap1<>(key, hash, value, equality); System.arraycopy(elems, offset, elemsNew, offset + 1, elems.length - offset); return new HashTrieMap<>(bitmap | mask, elemsNew, size + 1, equality); } } @Override AHashMap doRemoved(K key, int hash, int level) { final int index = (hash >>> level) & 0x1f; final int mask = (1 << index); final int offset = Integer.bitCount(bitmap & (mask - 1)); if ((bitmap & mask) != 0) { final AHashMap sub = elems[offset]; final AHashMap subNew = sub.doRemoved(key, hash, level + LEVEL_INCREMENT); if (subNew == sub) { return this; } else if (subNew.isEmpty()) { final int bitmapNew = bitmap ^ mask; if (bitmapNew != 0) { final AHashMap[] elemsNew = createArray(elems.length - 1); System.arraycopy(elems, 0, elemsNew, 0, offset); System.arraycopy(elems, offset + 1, elemsNew, offset, elems.length - offset - 1); final int sizeNew = size - sub.size(); if (elemsNew.length == 1 && ! (elemsNew[0] instanceof HashTrieMap)) { return elemsNew[0]; } else { return new HashTrieMap<>(bitmapNew, elemsNew, sizeNew, equality); } } else { return AHashMap.empty(equality); } } else if(elems.length == 1 && ! (subNew instanceof HashTrieMap)) { return subNew; } else { final AHashMap[] elemsNew = createArray(elems.length); System.arraycopy(elems, 0, elemsNew, 0, elems.length); elemsNew[offset] = subNew; final int sizeNew = size + (subNew.size() - sub.size()); return new HashTrieMap<>(bitmap, elemsNew, sizeNew, equality); } } else { return this; } } } private Object readResolve() { // rebuild the map in case hashCodes of entries were changed by serialization AHashMap result = AHashMap.empty (equality); for (APair entry: this) { result = result.updated (entry._1, entry._2); } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy