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

com.carrotsearch.hppc.ObjectHashSet Maven / Gradle / Ivy

Go to download

High Performance Primitive Collections: data structures (maps, sets, lists, stacks, queues) generated for combinations of object and primitive types to conserve JVM memory and speed up execution.

There is a newer version: 0.10.0
Show newest version
  
package com.carrotsearch.hppc;

import java.util.*;

import com.carrotsearch.hppc.cursors.*;
import com.carrotsearch.hppc.predicates.*;
import com.carrotsearch.hppc.procedures.*;

import static com.carrotsearch.hppc.HashContainers.*;
import static com.carrotsearch.hppc.Containers.*;

/**
 * A hash set of Objects, implemented using using open addressing
 * with linear probing for collision resolution.
 *
 * @see HPPC interfaces diagram
 */
  @SuppressWarnings("unchecked")  
 @com.carrotsearch.hppc.Generated(
    date = "2021-06-08T13:12:53+0200",
    value = "KTypeHashSet.java") 
public class ObjectHashSet
  extends AbstractObjectCollection 
  implements   
             ObjectLookupContainer, 
             ObjectSet,
             Preallocable,
             Cloneable,
             Accountable {
  /** The hash array holding keys. */
  public    
                   Object [] 
            
                   keys;

  /**
   * The number of stored keys (assigned key slots), excluding the special 
   * "empty" key, if any.
   * 
   * @see #size()
   * @see #hasEmptyKey
   */
  protected int assigned;

  /**
   * Mask for slot scans in {@link #keys}.
   */
  protected int mask;

  /**
   * Expand (rehash) {@link #keys} when {@link #assigned} hits this value. 
   */
  protected int resizeAt;

  /**
   * Special treatment for the "empty slot" key marker.
   */
  protected boolean hasEmptyKey;

  /**
   * The load factor for {@link #keys}.
   */
  protected double loadFactor;

  /**
   * Seed used to ensure the hash iteration order is different from an iteration to another.
   */
  protected int iterationSeed;

  /**
   * New instance with sane defaults.
   * 
   * @see #ObjectHashSet(int, double)
   */
  public ObjectHashSet() {
    this(DEFAULT_EXPECTED_ELEMENTS, DEFAULT_LOAD_FACTOR);
  }

  /**
   * New instance with sane defaults.
   * 
   * @see #ObjectHashSet(int, double)
   */
  public ObjectHashSet(int expectedElements) {
    this(expectedElements, DEFAULT_LOAD_FACTOR);
  }

  /**
   * New instance with the provided defaults.
   * 
   * @param expectedElements
   *          The expected number of elements guaranteed not to cause a rehash (inclusive).
   * @param loadFactor
   *          The load factor for internal buffers. Insane load factors (zero, full capacity)
   *          are rejected by {@link #verifyLoadFactor(double)}.
   */
  public ObjectHashSet(int expectedElements, double loadFactor) {
    this.loadFactor = verifyLoadFactor(loadFactor);
    iterationSeed = HashContainers.nextIterationSeed();
    ensureCapacity(expectedElements);
  }

  /**
   * New instance copying elements from another {@link ObjectContainer}.
   */
  public ObjectHashSet(ObjectContainer container) {
    this(container.size());
    addAll(container);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean add(KType key) {
    if (((key) == null)) {
      assert ((keys[mask + 1]) == null);
      boolean added = !hasEmptyKey;
      hasEmptyKey = true;
      return added;
    } else {
      final KType [] keys = (KType[]) this.keys;
      final int mask = this.mask;
      int slot = hashKey(key) & mask;
      
      KType existing;
      while (!((existing = keys[slot]) == null)) {
        if (this.equals(existing,  key)) {
          return false;
        }
        slot = (slot + 1) & mask;
      }

      if (assigned == resizeAt) {
        allocateThenInsertThenRehash(slot, key);
      } else {
        keys[slot] = key;
      }
  
      assigned++;
      return true;
    }
  }

  /**
   * Adds all elements from the given list (vararg) to this set. 
   * 
   * @return Returns the number of elements actually added as a result of this
   *         call (not previously present in the set).
   */
  /*  */
  @SafeVarargs
  /*  */
  public final int addAll(KType... elements) {
    ensureCapacity(elements.length);
    int count = 0;
    for (KType e : elements) {
      if (add(e)) {
        count++;
      }
    }
    return count;
  }

  /**
   * Adds all elements from the given {@link ObjectContainer} to this set.
   * 
   * @return Returns the number of elements actually added as a result of this
   *         call (not previously present in the set).
   */
  public int addAll(ObjectContainer container) {
    ensureCapacity(container.size());
    return addAll((Iterable>) container);
  }

  /**
   * Adds all elements from the given iterable to this set.
   * 
   * @return Returns the number of elements actually added as a result of this
   *         call (not previously present in the set).
   */
  public int addAll(Iterable> iterable) {
    int count = 0;
    for (ObjectCursor cursor : iterable) {
      if (add(cursor.value)) {
        count++;
      }
    }
    return count;
  }

  /**
   * {@inheritDoc}
   */
  @Override
    
  public Object[] toArray() {
    
    final KType[] cloned = ((KType[]) new Object [size()]);
    int j = 0;
    if (hasEmptyKey) {
      cloned[j++] = null;
    }

    final KType[] keys = (KType[]) this.keys;
    int seed = nextIterationSeed();
    int inc = iterationIncrement(seed);
    for (int i = 0, mask = this.mask, slot = seed & mask; i <= mask; i++, slot = (slot + inc) & mask) {
      KType existing;
      if (!((existing = keys[slot]) == null)) {
        cloned[j++] = existing;
      }
    }

    return cloned;
  }

  /**
   * An alias for the (preferred) {@link #removeAll}.
   */
  public boolean remove(KType key) {
    if (((key) == null)) {
      boolean hadEmptyKey = hasEmptyKey;
      hasEmptyKey = false;
      return hadEmptyKey;
    } else {
      final KType [] keys = (KType[]) this.keys;
      final int mask = this.mask;
      int slot = hashKey(key) & mask;
      
      KType existing;
      while (!((existing = keys[slot]) == null)) {
        if (this.equals(existing,  key)) {
          shiftConflictingKeys(slot);
          return true;
        }
        slot = (slot + 1) & mask;
      }
      return false;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int removeAll(KType key) {
    return remove(key) ? 1 : 0;
  }

  /**
   * Removes all keys present in a given container.
   *
   * @return Returns the number of elements actually removed as a result of this call.
   */
  public int removeAll(ObjectContainer other) {
    final int before = size();

    // Try to iterate over the smaller set or over the container that isn't implementing
    // efficient contains() lookup.

    if (other.size() >= size() &&
            other instanceof ObjectLookupContainer) {
      if (hasEmptyKey && other.contains(null)) {
        hasEmptyKey = false;
      }

      final KType[] keys = (KType[]) this.keys;
      for (int slot = 0, max = this.mask; slot <= max;) {
        KType existing;
        if (!((existing = keys[slot]) == null) && other.contains(existing)) {
          // Shift, do not increment slot.
          shiftConflictingKeys(slot);
        } else {
          slot++;
        }
      }
    } else {
      for (ObjectCursor c : other) {
        remove((KType) c.value);
      }
    }

    return before - size();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int removeAll(ObjectPredicate predicate) {
    int before = size();

    if (hasEmptyKey) {
      if (predicate.apply(null)) {
        hasEmptyKey = false;
      }
    }

    final KType[] keys = (KType[]) this.keys;
    for (int slot = 0, max = this.mask; slot <= max;) {
      KType existing;
      if (!((existing = keys[slot]) == null)) {
        if (predicate.apply(existing)) {
          shiftConflictingKeys(slot);
          continue; // Repeat the check for the same slot i (shifted).
        }
      }
      slot++;
    }

    return before - size();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean contains(KType key) {
    if (((key) == null)) {
      return hasEmptyKey;
    } else {
      final KType [] keys = (KType[]) this.keys;
      final int mask = this.mask;
      int slot = hashKey(key) & mask;
      KType existing;
      while (!((existing = keys[slot]) == null)) {
        if (this.equals(existing,  key)) {
          return true;
        }
        slot = (slot + 1) & mask;
      }
      return false;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void clear() {
    assigned = 0;
    hasEmptyKey = false;
    Arrays.fill(keys, null);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void release() {
    assigned = 0;
    hasEmptyKey = false;
    keys = null;
    ensureCapacity(Containers.DEFAULT_EXPECTED_ELEMENTS);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isEmpty() {
    return size() == 0;
  }

  /**
   * Ensure this container can hold at least the
   * given number of elements without resizing its buffers.
   * 
   * @param expectedElements The total number of elements, inclusive.
   */
  @Override
  public void ensureCapacity(int expectedElements) {
    if (expectedElements > resizeAt || keys == null) {
      final KType[] prevKeys = (KType[]) this.keys;
      allocateBuffers(minBufferSize(expectedElements, loadFactor));
      if (prevKeys != null && !isEmpty()) {
        rehash(prevKeys);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int size() {
    return assigned + (hasEmptyKey ? 1 : 0);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int hashCode() {
    int h = hasEmptyKey ? 0xDEADBEEF : 0;
    final KType[] keys = (KType[]) this.keys;
    for (int slot = mask; slot >= 0; slot--) {
      KType existing;
      if (!((existing = keys[slot]) == null)) {
        h += BitMixer.mix(existing);
      }
    }
    return h;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean equals(Object obj) {
    return obj != null &&
           getClass() == obj.getClass() &&
           sameKeys(getClass().cast(obj));
  }

  /**
   * Return true if all keys of some other container exist in this container.
     * Equality comparison is performed with this object's {@link #equals(Object, Object)} 
     * method.
   */
  private boolean sameKeys(ObjectSet other) {
    if (other.size() != size()) {
      return false;
    }

    for (ObjectCursor c : other) {
      if (!contains((KType) c.value)) {
        return false;
      }
    }

    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public ObjectHashSet clone() {
    try {
      /*  */
      ObjectHashSet cloned = (ObjectHashSet) super.clone();
      cloned.keys = keys.clone();
      cloned.hasEmptyKey = hasEmptyKey;
      cloned.iterationSeed = HashContainers.nextIterationSeed();
      return cloned;
    } catch (CloneNotSupportedException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Iterator> iterator() {
    return new EntryIterator();
  }

  @Override
  public long ramBytesAllocated() {
    // int: assigned, mask, keyMixer, resizeAt
    // double: loadFactor
    // boolean: hasEmptyKey
    return RamUsageEstimator.NUM_BYTES_OBJECT_HEADER + 4 * Integer.BYTES + Double.BYTES + 1 +
            RamUsageEstimator.shallowSizeOfArray(keys);
  }

  @Override
  public long ramBytesUsed() {
    // int: assigned, mask, keyMixer, resizeAt
    // double: loadFactor
    // boolean: hasEmptyKey
    return RamUsageEstimator.NUM_BYTES_OBJECT_HEADER + 4 * Integer.BYTES + Double.BYTES + 1 +
            RamUsageEstimator.shallowUsedSizeOfArray(keys, size());
  }

  /**
   * Provides the next iteration seed used to build the iteration starting slot and offset increment.
   * This method does not need to be synchronized, what matters is that each thread gets a sequence of varying seeds.
   */
  protected int nextIterationSeed() {
    return iterationSeed = BitMixer.mixPhi(iterationSeed);
  }

  /**
   * An iterator implementation for {@link #iterator}.
   */
  protected final class EntryIterator extends AbstractIterator> {
    private final ObjectCursor cursor;
    private final int increment;
    private int index;
    private int slot;

    public EntryIterator() {
      cursor = new ObjectCursor();
      int seed = nextIterationSeed();
      increment = iterationIncrement(seed);
      slot = seed & mask;
    }

    @Override
    protected ObjectCursor fetch() {
      final int mask = ObjectHashSet.this.mask;
      while (index <= mask) {
        KType existing;
        index++;
        slot = (slot + increment) & mask;
        if (!((existing = (KType) keys[slot]) == null)) {
          cursor.index = slot;
          cursor.value = existing;
          return cursor;
        }
      }

      if (index == mask + 1 && hasEmptyKey) {
        cursor.index = index++;
        cursor.value = null;
        return cursor;
      }

      return done();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public > T forEach(T procedure) {
    if (hasEmptyKey) {
      procedure.apply(null);
    }

    final KType[] keys = (KType[]) this.keys;
    int seed = nextIterationSeed();
    int inc = iterationIncrement(seed);
    for (int i = 0, mask = this.mask, slot = seed & mask; i <= mask; i++, slot = (slot + inc) & mask) {
      KType existing;
      if (!((existing = keys[slot]) == null)) {
        procedure.apply(existing);
      }
    }

    return procedure;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public > T forEach(T predicate) {
    if (hasEmptyKey) {
      if (!predicate.apply(null)) {
        return predicate;
      }
    }

    final KType[] keys = (KType[]) this.keys;
    int seed = nextIterationSeed();
    int inc = iterationIncrement(seed);
    for (int i = 0, mask = this.mask, slot = seed & mask; i <= mask; i++, slot = (slot + inc) & mask) {
      KType existing;
      if (!((existing = keys[slot]) == null)) {
        if (!predicate.apply(existing)) {
          break;
        }
      }
    }

    return predicate;
  }

  /**
   * Create a set from a variable number of arguments or an array of
   * Object. The elements are copied from the argument to the
   * internal buffer.
   */
  /*  */
  @SafeVarargs
  /*  */
  public static  ObjectHashSet from(KType... elements) {
    final ObjectHashSet set = new ObjectHashSet(elements.length);
    set.addAll(elements);
    return set;
  }

  /**
   * Returns a hash code for the given key.
   *
   * 

The output from this function should evenly distribute keys across the * entire integer range.

*/ protected int hashKey(KType key) { assert !((key) == null); // Handled as a special case (empty slot marker). return BitMixer.mixPhi(key); } /** * Returns a logical "index" of a given key that can be used to speed up * follow-up logic in certain scenarios (conditional logic). * * The semantics of "indexes" are not strictly defined. Indexes may * (and typically won't be) contiguous. * * The index is valid only between modifications (it will not be affected * by read-only operations). * * @see #indexExists * @see #indexGet * @see #indexInsert * @see #indexReplace * * @param key * The key to locate in the set. * @return A non-negative value of the logical "index" of the key in the set * or a negative value if the key did not exist. */ public int indexOf(KType key) { final int mask = this.mask; if (((key) == null)) { return hasEmptyKey ? mask + 1 : ~(mask + 1); } else { final KType[] keys = (KType[]) this.keys; int slot = hashKey(key) & mask; KType existing; while (!((existing = keys[slot]) == null)) { if (this.equals(existing, key)) { return slot; } slot = (slot + 1) & mask; } return ~slot; } } /** * @see #indexOf * * @param index The index of a given key, as returned from {@link #indexOf}. * @return Returns true if the index corresponds to an existing key * or false otherwise. This is equivalent to checking whether the index is * a positive value (existing keys) or a negative value (non-existing keys). */ public boolean indexExists(int index) { assert index < 0 || (index >= 0 && index <= mask) || (index == mask + 1 && hasEmptyKey); return index >= 0; } /** * Returns the exact value of the existing key. This method makes sense for sets * of objects which define custom key-equality relationship. * * @see #indexOf * * @param index The index of an existing key. * @return Returns the equivalent key currently stored in the set. * @throws AssertionError If assertions are enabled and the index does * not correspond to an existing key. */ public KType indexGet(int index) { assert index >= 0 : "The index must point at an existing key."; assert index <= mask || (index == mask + 1 && hasEmptyKey); return (KType) keys[index]; } /** * Replaces the existing equivalent key with the given one and returns any previous value * stored for that key. * * @see #indexOf * * @param index The index of an existing key. * @param equivalentKey The key to put in the set as a replacement. Must be equivalent to * the key currently stored at the provided index. * @return Returns the previous key stored in the set. * @throws AssertionError If assertions are enabled and the index does * not correspond to an existing key. */ public KType indexReplace(int index, KType equivalentKey) { assert index >= 0 : "The index must point at an existing key."; assert index <= mask || (index == mask + 1 && hasEmptyKey); assert this.equals(equivalentKey, keys[index]); KType previousValue = (KType) keys[index]; keys[index] = equivalentKey; return previousValue; } /** * Inserts a key for an index that is not present in the set. This method * may help in avoiding double recalculation of the key's hash. * * @see #indexOf * * @param index The index of a previously non-existing key, as returned from * {@link #indexOf}. * @throws AssertionError If assertions are enabled and the index does * not correspond to an existing key. */ public void indexInsert(int index, KType key) { assert index < 0 : "The index must not point at an existing key."; index = ~index; if (((key) == null)) { assert index == mask + 1; assert ((keys[index]) == null); hasEmptyKey = true; } else { assert ((keys[index]) == null); if (assigned == resizeAt) { allocateThenInsertThenRehash(index, key); } else { keys[index] = key; } assigned++; } } /** * Removes a key at an index previously acquired from {@link #indexOf}. * * @see #indexOf * * @param index The index of the key to remove, as returned from {@link #indexOf}. * @throws AssertionError If assertions are enabled and the index does * not correspond to an existing key. */ public void indexRemove(int index) { assert index >= 0 : "The index must point at an existing key."; assert index <= mask || (index == mask + 1 && hasEmptyKey); if (index > mask) { hasEmptyKey = false; } else { shiftConflictingKeys(index); } } @Override public String visualizeKeyDistribution(int characters) { return ObjectBufferVisualizer.visualizeKeyDistribution(keys, mask, characters); } /** * Validate load factor range and return it. Override and suppress if you need * insane load factors. */ protected double verifyLoadFactor(double loadFactor) { checkLoadFactor(loadFactor, MIN_LOAD_FACTOR, MAX_LOAD_FACTOR); return loadFactor; } /** * Rehash from old buffers to new buffers. */ protected void rehash(KType[] fromKeys) { assert HashContainers.checkPowerOfTwo(fromKeys.length - 1); // Rehash all stored keys into the new buffers. final KType[] keys = (KType[]) this.keys; final int mask = this.mask; KType existing; for (int i = fromKeys.length - 1; --i >= 0;) { if (!((existing = fromKeys[i]) == null)) { int slot = hashKey(existing) & mask; while (!((keys[slot]) == null)) { slot = (slot + 1) & mask; } keys[slot] = existing; } } } /** * Allocate new internal buffers. This method attempts to allocate * and assign internal buffers atomically (either allocations succeed or not). */ protected void allocateBuffers(int arraySize) { assert Integer.bitCount(arraySize) == 1; // Ensure no change is done if we hit an OOM. KType[] prevKeys = (KType[]) this.keys; try { int emptyElementSlot = 1; this.keys = ((KType[]) new Object [arraySize + emptyElementSlot]); } catch (OutOfMemoryError e) { this.keys = prevKeys; throw new BufferAllocationException( "Not enough memory to allocate buffers for rehashing: %,d -> %,d", e, this.keys == null ? 0 : size(), arraySize); } this.resizeAt = expandAtCount(arraySize, loadFactor); this.mask = arraySize - 1; } /** * This method is invoked when there is a new key to be inserted into * the buffer but there is not enough empty slots to do so. * * New buffers are allocated. If this succeeds, we know we can proceed * with rehashing so we assign the pending element to the previous buffer * (possibly violating the invariant of having at least one empty slot) * and rehash all keys, substituting new buffers at the end. */ protected void allocateThenInsertThenRehash(int slot, KType pendingKey) { assert assigned == resizeAt && (((KType) keys[slot]) == null) && !((pendingKey) == null); // Try to allocate new buffers first. If we OOM, we leave in a consistent state. final KType[] prevKeys = (KType[]) this.keys; allocateBuffers(nextBufferSize(mask + 1, size(), loadFactor)); assert this.keys.length > prevKeys.length; // We have succeeded at allocating new data so insert the pending key/value at // the free slot in the old arrays before rehashing. prevKeys[slot] = pendingKey; // Rehash old keys, including the pending key. rehash(prevKeys); } /** * Shift all the slot-conflicting keys allocated to (and including) slot. */ protected void shiftConflictingKeys(int gapSlot) { final KType[] keys = (KType[]) this.keys; final int mask = this.mask; // Perform shifts of conflicting keys to fill in the gap. int distance = 0; while (true) { final int slot = (gapSlot + (++distance)) & mask; final KType existing = keys[slot]; if (((existing) == null)) { break; } final int idealSlot = hashKey(existing); final int shift = (slot - idealSlot) & mask; if (shift >= distance) { // Entry at this position was originally at or before the gap slot. // Move the conflict-shifted entry to the gap's position and repeat the procedure // for any entries to the right of the current position, treating it // as the new gap. keys[gapSlot] = existing; gapSlot = slot; distance = 0; } } // Mark the last found gap slot without a conflict as empty. keys[gapSlot] = null; assigned--; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy