001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.units.collections;
006
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Collection;
010import java.util.List;
011
012/**
013 * A variant on {@code java.util.HashMap<K, V>} that uses primitive long ints for map keys instead
014 * of autoboxed Long objects like would be used for a {@code Map<Long, V>}.
015 *
016 * @param <V> the type of the values stored in the map
017 */
018public class LongToObjectHashMap<V> {
019  private static final int kInitialSize = 0;
020  private static final int kInitialCapacity = 8; // NOTE: must be a power of two
021
022  /**
023   * The default load factor of the hashmap. If the ratio of the number of entries to the map's
024   * capacity exceeds this value, the map will be resized (doubled capacity) in order for more
025   * values to be easily inserted.
026   */
027  private static final double kLoadFactor = 75.00 / 100;
028
029  /** The current number of key-value pairs in the map. */
030  private int m_size = kInitialSize;
031
032  /**
033   * The current maximum capacity of the map. Note that it will be resized before m_size reaches
034   * this value.
035   */
036  private int m_capacity = kInitialCapacity;
037
038  /**
039   * The keys in the map. This is a sparse array, and the location of a key may not be equal to the
040   * result of calling {@link #bucket(long)} on that key. To handle hash collisions, if a bucket is
041   * already in use when trying to insert a value, the bucket number is incremented (wrapping around
042   * to 0 if it's equal to m_capacity) and <i>that</i> bucket is checked to see if it's available.
043   * This process continues until an empty bucket is found (which is guaranteed because m_size is
044   * always less than m_capacity).
045   */
046  private long[] m_keys = new long[m_capacity];
047
048  /** Tracks which buckets are actually used (have a key-value mapping). */
049  private boolean[] m_uses = new boolean[m_capacity];
050
051  /**
052   * The values in the map. See the documentation for m_keys for how indexing into this array works.
053   */
054  @SuppressWarnings("unchecked")
055  private V[] m_values = (V[]) new Object[m_capacity];
056
057  /**
058   * Puts a value {@code value} corresponding to key {@code key} in the map.
059   *
060   * @param key the associated key
061   * @param value the value to insert
062   * @return the previous value that was mapped to the key, or null if no such value existed
063   */
064  public V put(long key, V value) {
065    int bucket = bucket(key);
066
067    // Increment the bucket until we hit an open space (there's always going to be at least one)
068    while (m_uses[bucket]) {
069      if (m_keys[bucket] == key) {
070        // replace the existing value
071        var oldValue = m_values[bucket];
072        m_values[bucket] = value;
073        return oldValue;
074      }
075      bucket = safeIncrement(bucket);
076    }
077
078    m_uses[bucket] = true;
079    m_keys[bucket] = key;
080    m_values[bucket] = value;
081    m_size++;
082
083    if (m_size > maxSize()) {
084      grow();
085    }
086    return null;
087  }
088
089  /**
090   * Gets the value associated with the given key.
091   *
092   * @param key the key to retrieve the value for
093   * @return the value mapped to the key, or null if the key is not in the map
094   */
095  public V get(long key) {
096    int bucket = bucket(key);
097    while (m_uses[bucket]) {
098      if (m_keys[bucket] == key) {
099        // found it
100        return m_values[bucket];
101      }
102      bucket = safeIncrement(bucket);
103    }
104    return null;
105  }
106
107  /**
108   * Removes the value associated with the given key and returns it.
109   *
110   * @param key the key to remove
111   * @return the value corresponding to the key, or null if the key is not in the map
112   */
113  public V remove(long key) {
114    int bucket = bucket(key);
115    while (m_uses[bucket]) {
116      if (m_keys[bucket] == key) {
117        // found it
118        // TODO: Shrink the map when below a certain load factor
119        //       Current use cases don't remove elements from the map, so there's not much use
120        //       for shrinking at the moment.
121        m_size--;
122        m_keys[bucket] = 0L;
123        m_uses[bucket] = false;
124
125        var oldValue = m_values[bucket];
126        m_values[bucket] = null;
127        return oldValue;
128      }
129      bucket = safeIncrement(bucket);
130    }
131
132    return null;
133  }
134
135  /**
136   * Checks if a key is contained in the map.
137   *
138   * @param key the key to check
139   * @return true if the key has an associated value, false if not
140   */
141  public boolean containsKey(long key) {
142    int bucket = bucket(key);
143    while (m_uses[bucket]) {
144      if (m_keys[bucket] == key) {
145        return true;
146      }
147      bucket = safeIncrement(bucket);
148    }
149    return false;
150  }
151
152  /** Clears and removes all entries from the map. */
153  public void clear() {
154    if (m_size == 0) {
155      // Nothing to do
156      return;
157    }
158    m_size = 0;
159
160    Arrays.fill(m_uses, false);
161    Arrays.fill(m_keys, 0L);
162    Arrays.fill(m_values, null);
163  }
164
165  /**
166   * Gets the number of key-value pairs currently contained in the map.
167   *
168   * @return the current size of the map
169   */
170  public int size() {
171    return m_size;
172  }
173
174  // package-private for tests
175  int capacity() {
176    return m_capacity;
177  }
178
179  /**
180   * Checks if the map contains any entries.
181   *
182   * @return true if at least one entry is present, false otherwise
183   */
184  public boolean isEmpty() {
185    return m_size == 0;
186  }
187
188  /**
189   * Gets the keys contained in the map. Ordering is not guaranteed. The returned set is read-only
190   * and immutable. This uses a custom class for primitive long values to avoid unnecessary
191   * autoboxing to {@code java.lang.Long}.
192   *
193   * @return a read-only set of keys
194   */
195  public ReadOnlyPrimitiveLongSet keySet() {
196    // copy the sparse key array into a compact array
197    final long[] keys = new long[m_size];
198    int i = 0;
199    for (int bucket = 0; bucket < m_capacity; bucket++) {
200      if (m_uses[bucket]) {
201        keys[i] = m_keys[bucket];
202        i++;
203      }
204    }
205
206    return new ReadOnlyPrimitiveLongSet(keys);
207  }
208
209  /**
210   * Gets the values contained in the map. Ordering is not guaranteed. The returned collection is
211   * read-only and immutable.
212   *
213   * @return a read-only collection of values
214   */
215  public Collection<V> values() {
216    Collection<V> values = new ArrayList<>();
217    for (int bucket = 0; bucket < m_capacity; bucket++) {
218      if (m_uses[bucket]) {
219        values.add(m_values[bucket]);
220      }
221    }
222    return List.copyOf(values); // return a readonly copy
223  }
224
225  @FunctionalInterface
226  public interface IteratorFunction<V> {
227    void accept(long key, V value);
228  }
229
230  /**
231   * Iterates over every key-value pair in the map and passes them to the given function.
232   *
233   * @param function the function to apply to every key-value pair.
234   */
235  public void forEach(IteratorFunction<? super V> function) {
236    for (int bucket = 0; bucket < m_capacity; bucket++) {
237      if (m_uses[bucket]) {
238        function.accept(m_keys[bucket], m_values[bucket]);
239      }
240    }
241  }
242
243  private void grow() {
244    final int currentSize = m_size;
245    final int oldCapacity = m_capacity;
246    if (oldCapacity * kLoadFactor >= currentSize) {
247      // We're below the maximum allowed size for the current capacity
248      // Nothing to do
249      return;
250    }
251
252    final int newCapacity = oldCapacity * 2;
253    final int newMask = newCapacity - 1;
254
255    final boolean[] oldUses = m_uses;
256    final long[] oldKeys = m_keys;
257    final V[] oldValues = m_values;
258
259    final boolean[] newUses = new boolean[newCapacity];
260    final long[] newKeys = new long[newCapacity];
261    @SuppressWarnings("unchecked")
262    final V[] newValues = (V[]) new Object[newCapacity];
263
264    for (int oldBucket = 0; oldBucket < oldCapacity; oldBucket++) {
265      if (!oldUses[oldBucket]) {
266        // Bucket is empty, skip
267        continue;
268      }
269      final long key = oldKeys[oldBucket];
270      final V value = oldValues[oldBucket];
271
272      int newBucket = (int) (hash(key) & newMask);
273      while (newUses[newBucket]) {
274        newBucket = (newBucket + 1) & newMask;
275      }
276
277      newUses[newBucket] = true;
278      newKeys[newBucket] = key;
279      newValues[newBucket] = value;
280    }
281
282    m_capacity = newCapacity;
283    m_uses = newUses;
284    m_keys = newKeys;
285    m_values = newValues;
286  }
287
288  private int maxSize() {
289    return (int) (m_capacity * kLoadFactor);
290  }
291
292  /**
293   * Calculates a hashcode for an input key. Does some bit shuffling to account for poor hash
294   * functions.
295   *
296   * @param key the key to hash
297   * @return a hashcode for the input key
298   */
299  private long hash(long key) {
300    return 31 + (key ^ (key >>> 15) ^ (key >>> 31) ^ (key << 31));
301  }
302
303  /**
304   * The mask to use when translating a hashcode to a bucket index. Relies on m_capacity being a
305   * power of two.
306   */
307  private int mask() {
308    return m_capacity - 1;
309  }
310
311  /**
312   * Calculates the desired bucket index for a particular key. Does nothing to handle the case where
313   * the calculated index is already in use by another key.
314   *
315   * @param key the key to get the bucket for
316   * @return the desired bucket index
317   */
318  private int bucket(long key) {
319    var hash = hash(key);
320    return (int) (hash & mask());
321  }
322
323  /**
324   * Increments a bucket index by 1, wrapping around to 0 if the index is already at the maximum.
325   *
326   * @param bucket the index to increment
327   * @return the incremented bucket index
328   */
329  private int safeIncrement(int bucket) {
330    return (bucket + 1) & mask();
331  }
332}