/* * Copyright (C) 2009-2012 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.util; import java.util.AbstractSet; import java.util.Iterator; public class CuckooHashSet<E> extends AbstractSet<E> { /** * Default hash size. Hash size is the power of two of this number. */ private static final int DEFAULT_SIZE_LOG_2 = 5; protected static class StashList<E> { E mEntry; StashList<E> mNext; public StashList(E entry, StashList<E> next) { this.mEntry = entry; this.mNext = next; } public E getEntry() { return mEntry; } public StashList<E> getNext() { return mNext; } } protected int mLog2buckets = 5; protected Object[] mBuckets; protected StashList<E> mStashList; private int mSize; public CuckooHashSet(int size) { this.mLog2buckets = log2(2 * size); this.mBuckets = new Object[1 << mLog2buckets]; } public CuckooHashSet() { this.mLog2buckets = DEFAULT_SIZE_LOG_2; this.mBuckets = new Object[1 << DEFAULT_SIZE_LOG_2]; } /** * The hash function. This must have good bit distributing properties. * We use Jenkins hash function on object hashcode. * @param o the object to hash * @return the hash code. */ protected int hash(Object o) { return hashJenkins(o.hashCode()); } protected static int hashJenkins(int hash) { hash += (hash << 12);// NOCHECKSTYLE hash ^= (hash >>> 22);// NOCHECKSTYLE hash += (hash << 4);// NOCHECKSTYLE hash ^= (hash >>> 9);// NOCHECKSTYLE hash += (hash << 10);// NOCHECKSTYLE hash ^= (hash >>> 2);// NOCHECKSTYLE hash += (hash << 7);// NOCHECKSTYLE hash ^= (hash >>> 12);// NOCHECKSTYLE return hash; } protected final int hash1(int hash) { return hash & (mBuckets.length - 1); } protected final int hash2(int hash) { /* This computes (hash % (n^2)) % n-1, with n = buckets.length, * where 0 is mapped to n-1. * This may return 0 only if hash % (n^2) is 0; this is so unlikely * that it won't degrade performance of cuckoo hashing. */ hash = ((hash >>> mLog2buckets) & (mBuckets.length - 1)) + (hash & (mBuckets.length - 1)); hash = (hash + (hash >>> mLog2buckets)) & (mBuckets.length - 1); return hash; } private boolean containsStash(Object object) { StashList<E> stash = this.mStashList; while (stash != null) { if (object.equals(stash.mEntry)) { return true; } stash = stash.mNext; } return false; } @Override public boolean contains(Object object) { final int hash = hash(object); final int hash1 = hash1(hash); if (object.equals(mBuckets[hash1])) { return true; } if (object.equals(mBuckets[hash2(hash) ^ hash1])) { return true; } return mStashList != null && containsStash(object); } @SuppressWarnings("unchecked") private void resize() { final Object[] oldbuckets = mBuckets; StashList<E> oldstash = mStashList; mStashList = null; mLog2buckets++; mBuckets = new Object[1 << mLog2buckets]; for (int i = 0; i < oldbuckets.length; i++) { if (oldbuckets[i] != null) { add_internal(hash1(hash(oldbuckets[i])), (E) oldbuckets[i]); } } while (oldstash != null) { add_internal(hash1(hash(oldstash.mEntry)), oldstash.mEntry); oldstash = oldstash.mNext; } } @SuppressWarnings("unchecked") private void add_internal(int hash, E toAdd) { int maxIter = mBuckets.length >> 2; while (true) { assert checkpos(hash); final Object spill = mBuckets[hash]; mBuckets[hash] = toAdd; assert checkpos(hash); if (spill == null) { return; } toAdd = (E) spill; hash ^= hash2(hash(toAdd)); if (maxIter-- == 0) { if (3 * mSize < mBuckets.length) { /* Use stash instead of resizing */ mStashList = new StashList<E>(toAdd, mStashList); return; } else { resize(); maxIter = mBuckets.length >> 2; hash = hash1(hash(toAdd)); } } } } private boolean checkpos(int i) { if (mBuckets[i] != null) { final int hash = hash(mBuckets[i]); final int hash1 = hash1(hash); final int hash2 = hash1 ^ hash2(hash); assert(hash1 == i || hash2 == i); } return true; } private boolean invariant() { assert(mSize >= 0); int cnt = 0; for (int i = 0; i < mBuckets.length; i++) { assert checkpos(i); if (mBuckets[i] != null) { cnt++; } } StashList<E> stash = mStashList; while (stash != null) { cnt++; stash = stash.mNext; } assert(mSize == cnt); return true; } @Override public boolean add(E toAdd) { final int hash = hash(toAdd); final int hash1 = hash1(hash); if (toAdd.equals(mBuckets[hash1])) { return false; } if (toAdd.equals(mBuckets[hash2(hash) ^ hash1])) { return false; } if (mStashList != null && containsStash(toAdd)) { return false; } if (mBuckets[hash1] == null) { mBuckets[hash1] = toAdd; } else { add_internal(hash1, toAdd); } mSize++; return true; } @Override public boolean remove(Object toRemove) { final int hash = hash(toRemove); int hash1 = hash1(hash); if (toRemove.equals(mBuckets[hash1])) { mSize--; assert(mSize >= 0); mBuckets[hash1] = null; return true; } hash1 ^= hash2(hash); if (toRemove.equals(mBuckets[hash1])) { mSize--; assert mSize >= 0; mBuckets[hash1] = null; return true; } if (mStashList == null) { return false; } StashList<E> pre = null; StashList<E> stash = this.mStashList; while (stash != null) { if (toRemove.equals(stash.mEntry)) { mSize--; assert mSize >= 0; if (pre == null) { this.mStashList = stash.mNext; } else { pre.mNext = stash.mNext; } assert invariant(); return true; } pre = stash; stash = stash.mNext; } return false; } private final static int log2(int size) { int i,j; for (i = 4, j = 2; i < size; i += i, j++) { /*empty*/; } return j; } @Override public Iterator<E> iterator() { return new Iterator<E>() { int mLastPos = -1; int mPos = 0; StashList<E> mPre = null; StashList<E> mCurrent = null; @Override public boolean hasNext() { while (mPos < mBuckets.length && mBuckets[mPos] == null) { mPos++; } if (mPos < mBuckets.length) { return true; } if (mCurrent != null) { return mCurrent.mNext != null; } return mStashList != null; } @Override @SuppressWarnings("unchecked") public E next() { while (mPos < mBuckets.length && mBuckets[mPos] == null) { mPos++; } mLastPos = mPos; if (mPos < mBuckets.length) { return (E) mBuckets[mPos++]; } if (mCurrent == null) { mCurrent = mStashList; } else { mPre = mCurrent; mCurrent = mCurrent.mNext; } return mCurrent.mEntry; } @Override public void remove() { if (mLastPos < mBuckets.length) { mBuckets[mLastPos] = null; } else if (mPre == null) { mStashList = mCurrent.mNext; mCurrent = null; } else { mPre.mNext = mCurrent.mNext; mCurrent = mPre; } mSize--; assert(mSize >= 0); } }; } @Override public int size() { return mSize; } @Override public void clear() { mSize = 0; for (int i = 0; i < mBuckets.length; i++) { mBuckets[i] = null; } mStashList = null; } @SuppressWarnings("unchecked") public E removeSome() { if (mSize == 0) { return null; } mSize--; assert(mSize >= 0); if (mStashList != null) { final E entry = mStashList.mEntry; mStashList = mStashList.mNext; return entry; } for (int i = 0; /* empty */; i++) { if (mBuckets[i] != null) { final E entry = (E) mBuckets[i]; mBuckets[i] = null; return entry; } } } }