package de.blau.android.util.collections; import java.io.Serializable; import java.util.Arrays; import android.annotation.SuppressLint; import android.os.Build; /** * long HashSet * * Fast storage of unique long values, based on public domain code see http://unlicense.org from Mikhail * Vorontsov, see https://github.com/mikvor * * Currently doe not Implement the Iterable interface as returning primitive types would require Java 8 * * This code is not thread safe and requires external synchronization if inserts and removals need to be * made in a consistent fashion. * * @version 0.1 * @author simon */ @SuppressLint("UseSparseArrays") public class LongHashSet implements Serializable { /** * */ private static final long serialVersionUID = 1L; // NOTE if you change the // hashing algorithm you // need to increment // this private static final long FREE_KEY = 0; /** * Default fill factor */ private static final float DEFAULT_FILLFACTOR = 0.75f; /** * Default capacity */ private static final int DEFAULT_CAPACITY = 16; /** Keys and values */ private long[] m_data; /** Fill factor, must be between (0 and 1) */ private final float m_fillFactor; /** We will resize a map once it reaches this size */ private int m_threshold; /** Current map size */ private int m_size; /** Mask to calculate the original position */ private long m_mask; /** Do we have 'free' key in the map? */ private boolean m_hasFreeKey; /** * Create a new map with default values for capacity and fill factor */ public LongHashSet() { this(DEFAULT_CAPACITY, DEFAULT_FILLFACTOR); } /** * Create a new map with the specified size and the default fill factor * @param size initial capacity of the set */ public LongHashSet(final int size) { this(size, DEFAULT_FILLFACTOR); } /** * Create a new map with the specified size and fill factor * @param size initial capacity of the set * @param fillFactor fillfactor to us instead of the default */ private LongHashSet(final int size, final float fillFactor) { if (fillFactor <= 0 || fillFactor >= 1) { throw new IllegalArgumentException("FillFactor must be in (0, 1)"); } if (size <= 0) { throw new IllegalArgumentException("Size must be positive!"); } final int capacity = Tools.arraySize(size, fillFactor); m_mask = capacity - 1; m_fillFactor = fillFactor; m_data = new long[capacity]; m_threshold = (int) (capacity * fillFactor); m_hasFreeKey = false; } /** * Create a shallow copy of the specified set * @param set the set to copy */ @SuppressLint("NewApi") public LongHashSet(LongHashSet set) { m_mask = set.m_mask; m_fillFactor = set.m_fillFactor; m_threshold = set.m_threshold; m_size = set.m_size; m_hasFreeKey = set.m_hasFreeKey; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { m_data = Arrays.copyOf(set.m_data, set.m_data.length); } else { // sigh m_data = new long[set.m_data.length]; System.arraycopy(set.m_data, 0, m_data, 0, m_data.length); } } /** * Add a single element to the map * @param value value to add */ public void put(final long value) { if (value == FREE_KEY) { m_hasFreeKey = true; return; } int ptr = (int) ((Tools.phiMix(value) & m_mask)); long e = m_data[ptr]; if (e == FREE_KEY) // end of chain already { m_data[ptr] = value; if (m_size >= m_threshold) { rehash(m_data.length * 2); // size is set inside } else { ++m_size; } return; } else if (e == value) { // we check FREE and REMOVED prior to this call return; } while (true) { ptr = (int) ((ptr + 1) & m_mask); // the next index calculation e = m_data[ptr]; if (e == FREE_KEY) { m_data[ptr] = value; if (m_size >= m_threshold) { rehash(m_data.length * 2); // size is set inside } else { ++m_size; } return; } else if (e == value) { return; } } } /** * Add all elements from map * @param map */ // public void putAll(LongHashSet map) { // ensureCapacity(m_data.length + map.size()); // for (Long e : map) { // trivial implementation for now // put(e); // } // } /** * Remove element with the specified value from the set, * does not shrink the underlying array * @param value value to remove * @return true if found and removed */ public boolean remove(final long value) { if (value == FREE_KEY) { m_hasFreeKey = false; return true; } int ptr = (int) (Tools.phiMix(value) & m_mask); long e = m_data[ptr]; if (e == FREE_KEY) { return false; // end of chain already } else if (e == value) // we check FREE and REMOVED prior to this call { --m_size; shiftKeys( ptr ); return true; } while (true) { ptr = (int) ((ptr + 1) & m_mask); // that's next index calculation e = m_data[ptr]; if (e == FREE_KEY) { return false; } else if (e == value) { --m_size; shiftKeys( ptr ); return true; } } } private int shiftKeys(int pos) { // Shift entries with the same hash. int last, slot; long k; final long[] data = this.m_data; while ( true ) { pos = (int) (((last = pos) + 1) & m_mask); while ( true ) { if ((k = data[pos]) == FREE_KEY) { data[last] = FREE_KEY; return last; } slot = (int) ((Tools.phiMix(k) & m_mask));//calculate the starting slot for the current key if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) break; pos = (int) ((pos + 1) & m_mask); //go to the next entry } data[last] = k; } } /** * Return true if the map contains an object with the specified value * @param value * @return true if value was found */ public boolean contains(long value) { if (value == FREE_KEY) { return true; } int ptr = (int) ((Tools.phiMix(value) & m_mask)); long e = m_data[ptr]; if (e == FREE_KEY) { return false; } if (e == value) { // note this assumes REMOVED_KEY doesn't match return true; } while (true) { ptr = (int) ((ptr + 1) & m_mask); // the next index e = m_data[ptr]; if (e == FREE_KEY) { return false; } if (e == value) { return true; } } } /** * Return all values in the set. * Note: they are returned unordered * @return array containing the values */ public long[] values() { int found = 0; long[] result = new long[m_size]; for (int i=0; i< m_data.length; i++) { long v =m_data[i]; if (v != FREE_KEY) { result[found] = v; found++; if (found >= m_size) { // found all break; } } } return result; } /** * Return the number of elements in the map * @return the element count */ public int size() { return m_size; } /** * Return if the set is empty * @return true if the set is empty */ public boolean isEmpty() { return m_size == 0; } /** * Remove all elements from the set */ public void clear() { for (int i=0;i<m_data.length;i++) { m_data[i] = FREE_KEY; } m_size = 0; m_hasFreeKey = false; } /** * Provide capacity for minimumCapacity elements * without need for growing the underlying array and * rehashing. * @param minimumCapacity */ public void ensureCapacity(int minimumCapacity) { int newCapacity = Tools.arraySize(minimumCapacity, m_fillFactor); if (newCapacity > m_data.length) { rehash(newCapacity); } } private void rehash(final int newCapacity) { m_threshold = (int) (newCapacity * m_fillFactor); m_mask = newCapacity - 1; final int oldCapacity = m_data.length; final long[] oldData = m_data; m_data = new long[newCapacity]; m_size = 0; for (int i = 0; i < oldCapacity; i++) { final long e = oldData[i]; if (e != FREE_KEY) { put(e); } } } }