/* * Copyright (C) 2014 University of Freiburg * * This file is part of SMTInterpol. * * SMTInterpol is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SMTInterpol is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with SMTInterpol. If not, see <http://www.gnu.org/licenses/>. */ package de.uni_freiburg.informatik.ultimate.smtinterpol.model; import java.util.NoSuchElementException; /** * A bidirectional map from int to E. * @author Juergen Christ */ public class BidiMap<E> { private static class Entry<E> { final int mIdx; final E mVal; final int mHash; Entry<E> mNextIdx; Entry<E> mNextVal; public Entry(int idx, E val, int hash) { mIdx = idx; mVal = val; mHash = hash; mNextIdx = null; mNextVal = null; } public int getIdx() { return mIdx; } public E getValue() { return mVal; } @Override public String toString() { return "[" + mIdx + "," + mVal + "]"; } } private static int roundUpToPowerOf2(int number) { // assert number >= 0 : "number must be non-negative"; int rounded = number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (rounded = Integer.highestOneBit(number)) != 0 //NOPMD ? (Integer.bitCount(number) > 1) ? rounded << 1 : rounded : 1; return rounded; } private Entry<E>[] mIntTable; private Entry<E>[] mValTable; private int mSize; private int mThreshold; private float mLoadFactor; private Entry<E> mLastEntry; public final static float DEFAULT_LOAD_FACTOR = 0.75f; public final static int DEFAULT_SIZE = 8; public final static int MAXIMUM_CAPACITY = 1 << 30; // NOCHECKSTYLE public BidiMap() { this(DEFAULT_SIZE, DEFAULT_LOAD_FACTOR); } public BidiMap(int size) { this(size, DEFAULT_LOAD_FACTOR); } public BidiMap(int size, float loadFactor) { mSize = 0; mLoadFactor = loadFactor; init(roundUpToPowerOf2(size)); } @SuppressWarnings("unchecked") private void init(int size) { mIntTable = new Entry[size]; mValTable = new Entry[size]; mThreshold = (int) (size * mLoadFactor); } private final int hash(E val) { int h = 0; h ^= val.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); // NOCHECKSTYLE return h ^ (h >>> 7) ^ (h >>> 4); // NOCHECKSTYLE } private final int intBucketIdx(int idx) { return idx & (mIntTable.length - 1); } private final int valBucketIdx(int hash) { return hash & (mValTable.length - 1); } /** * Add a new mapping. Note that this map does not support null keys. If * either the key or the value is already known, the map is not changed and * <code>false</code> is returned. Otherwise, the new mapping is created. * @param idx The index. * @param val The value. * @return Did we actually change the mapping? * @throws NullPointerException If <code>val</code> is <code>null</code>. */ public boolean add(int idx, E val) { final int hash = hash(val); final Entry<E> newEntry = new Entry<E>(idx, val, hash); if (canInsert(newEntry)) { mLastEntry = null; if (++mSize >= mThreshold) { grow(); } insertInt(newEntry); insertVal(newEntry); return true; } mLastEntry = null; return false; } @SuppressWarnings("unchecked") private void grow() { int newCapacity = mIntTable.length << 1; if (newCapacity > MAXIMUM_CAPACITY) { newCapacity = MAXIMUM_CAPACITY; } mThreshold = (int) (newCapacity * mLoadFactor); // Rehashing final Entry<E>[] oldIntTable = mIntTable; final Entry<E>[] oldValTable = mValTable; mIntTable = new Entry[newCapacity]; mValTable = new Entry[newCapacity]; for (int i = 0; i < oldIntTable.length; ++i) { for (Entry<E> bucket = oldIntTable[i]; bucket != null; ) { final Entry<E> reinsert = bucket; bucket = bucket.mNextIdx; insertInt(reinsert); } } for (int i = 0; i < oldValTable.length; ++i) { for (Entry<E> bucket = oldValTable[i]; bucket != null; ) { final Entry<E> reinsert = bucket; bucket = bucket.mNextVal; insertVal(reinsert); } } } private Entry<E> getEntryByIdx(int idx) { final int b = intBucketIdx(idx); for (Entry<E> bucket = mIntTable[b]; bucket != null; bucket = bucket.mNextIdx) { if (bucket.mIdx == idx) { return mLastEntry = bucket; } } return null; } private Entry<E> getEntryByVal(E val) { final int hash = hash(val); final int b = valBucketIdx(hash); for (Entry<E> bucket = mValTable[b]; bucket != null; bucket = bucket.mNextVal) { // Compare on full hash to reduce equals comparisons if (bucket.mHash == hash && bucket.mVal.equals(val)) { return mLastEntry = bucket; } } return null; } private boolean canInsert(Entry<E> newEntry) { return getEntryByIdx(newEntry.mIdx) == null && getEntryByVal(newEntry.mVal) == null; } private void insertInt(Entry<E> newEntry) { final int idx = intBucketIdx(newEntry.mIdx); newEntry.mNextIdx = mIntTable[idx]; mIntTable[idx] = newEntry; } private void insertVal(Entry<E> newEntry) { final int idx = valBucketIdx(newEntry.mHash); newEntry.mNextVal = mValTable[idx]; mValTable[idx] = newEntry; } public E get(int idx) { if (mLastEntry != null && mLastEntry.mIdx == idx) { return mLastEntry.getValue(); } final Entry<E> bucket = getEntryByIdx(idx); return bucket == null ? null : bucket.getValue(); } public int get(E val) { if (mLastEntry != null) { final int hash = hash(val); if (mLastEntry.mHash == hash && mLastEntry.mVal.equals(val)) { return mLastEntry.getIdx(); } } final Entry<E> bucket = getEntryByVal(val); if (bucket == null) { throw new NoSuchElementException(); } return bucket.getIdx(); } public boolean containsIdx(int idx) { return getEntryByIdx(idx) != null; } public boolean containsVal(E val) { return getEntryByVal(val) != null; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append('{'); for (final Entry<E> outer : mIntTable) { for (Entry<E> bucket = outer; bucket != null; bucket = bucket.mNextIdx) { sb.append(bucket); } } sb.append('}'); return sb.toString(); } public int size() { return mSize; } }