OpenIntToDoubleHashMap.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.math4.legacy.linear;

  18. import org.apache.commons.math4.core.jdkmath.JdkMath;

  19. import java.io.IOException;
  20. import java.io.ObjectInputStream;
  21. import java.io.Serializable;
  22. import java.util.ConcurrentModificationException;
  23. import java.util.NoSuchElementException;

  24. /**
  25.  * Open addressed map from int to double.
  26.  * <p>This class provides a dedicated map from integers to doubles with a
  27.  * much smaller memory overhead than standard <code>java.util.Map</code>.</p>
  28.  * <p>This class is not synchronized. The specialized iterators returned by
  29.  * {@link #iterator()} are fail-fast: they throw a
  30.  * <code>ConcurrentModificationException</code> when they detect the map has been
  31.  * modified during iteration.</p>
  32.  * @since 2.0
  33.  */
  34. class OpenIntToDoubleHashMap implements Serializable { // Not in public API.

  35.     /** Status indicator for free table entries. */
  36.     protected static final byte FREE    = 0;

  37.     /** Status indicator for full table entries. */
  38.     protected static final byte FULL    = 1;

  39.     /** Status indicator for removed table entries. */
  40.     protected static final byte REMOVED = 2;

  41.     /** Serializable version identifier. */
  42.     private static final long serialVersionUID = -3646337053166149105L;

  43.     /** Load factor for the map. */
  44.     private static final float LOAD_FACTOR = 0.5f;

  45.     /** Default starting size.
  46.      * <p>This must be a power of two for bit mask to work properly. </p>
  47.      */
  48.     private static final int DEFAULT_EXPECTED_SIZE = 16;

  49.     /** Multiplier for size growth when map fills up.
  50.      * <p>This must be a power of two for bit mask to work properly. </p>
  51.      */
  52.     private static final int RESIZE_MULTIPLIER = 2;

  53.     /** Number of bits to perturb the index when probing for collision resolution. */
  54.     private static final int PERTURB_SHIFT = 5;

  55.     /** Keys table. */
  56.     private int[] keys;

  57.     /** Values table. */
  58.     private double[] values;

  59.     /** States table. */
  60.     private byte[] states;

  61.     /** Return value for missing entries. */
  62.     private final double missingEntries;

  63.     /** Current size of the map. */
  64.     private int size;

  65.     /** Bit mask for hash values. */
  66.     private int mask;

  67.     /** Modifications count. */
  68.     private transient int count;

  69.     /**
  70.      * Build an empty map with default size and using NaN for missing entries.
  71.      */
  72.     OpenIntToDoubleHashMap() {
  73.         this(DEFAULT_EXPECTED_SIZE, Double.NaN);
  74.     }

  75.     /**
  76.      * Build an empty map with default size.
  77.      * @param missingEntries value to return when a missing entry is fetched
  78.      */
  79.     OpenIntToDoubleHashMap(final double missingEntries) {
  80.         this(DEFAULT_EXPECTED_SIZE, missingEntries);
  81.     }

  82.     /**
  83.      * Build an empty map with specified size and using NaN for missing entries.
  84.      * @param expectedSize expected number of elements in the map
  85.      */
  86.     OpenIntToDoubleHashMap(final int expectedSize) {
  87.         this(expectedSize, Double.NaN);
  88.     }

  89.     /**
  90.      * Build an empty map with specified size.
  91.      * @param expectedSize expected number of elements in the map
  92.      * @param missingEntries value to return when a missing entry is fetched
  93.      */
  94.     OpenIntToDoubleHashMap(final int expectedSize,
  95.                            final double missingEntries) {
  96.         final int capacity = computeCapacity(expectedSize);
  97.         keys   = new int[capacity];
  98.         values = new double[capacity];
  99.         states = new byte[capacity];
  100.         this.missingEntries = missingEntries;
  101.         mask   = capacity - 1;
  102.     }

  103.     /**
  104.      * Copy constructor.
  105.      * @param source map to copy
  106.      */
  107.     OpenIntToDoubleHashMap(final OpenIntToDoubleHashMap source) {
  108.         final int length = source.keys.length;
  109.         keys = new int[length];
  110.         System.arraycopy(source.keys, 0, keys, 0, length);
  111.         values = new double[length];
  112.         System.arraycopy(source.values, 0, values, 0, length);
  113.         states = new byte[length];
  114.         System.arraycopy(source.states, 0, states, 0, length);
  115.         missingEntries = source.missingEntries;
  116.         size  = source.size;
  117.         mask  = source.mask;
  118.         count = source.count;
  119.     }

  120.     /**
  121.      * Compute the capacity needed for a given size.
  122.      * @param expectedSize expected size of the map
  123.      * @return capacity to use for the specified size
  124.      */
  125.     private static int computeCapacity(final int expectedSize) {
  126.         if (expectedSize == 0) {
  127.             return 1;
  128.         }
  129.         final int capacity   = (int) JdkMath.ceil(expectedSize / LOAD_FACTOR);
  130.         final int powerOfTwo = Integer.highestOneBit(capacity);
  131.         if (powerOfTwo == capacity) {
  132.             return capacity;
  133.         }
  134.         return nextPowerOfTwo(capacity);
  135.     }

  136.     /**
  137.      * Find the smallest power of two greater than the input value.
  138.      * @param i input value
  139.      * @return smallest power of two greater than the input value
  140.      */
  141.     private static int nextPowerOfTwo(final int i) {
  142.         return Integer.highestOneBit(i) << 1;
  143.     }

  144.     /**
  145.      * Get the stored value associated with the given key.
  146.      * @param key key associated with the data
  147.      * @return data associated with the key
  148.      */
  149.     public double get(final int key) {

  150.         final int hash  = hashOf(key);
  151.         int index = hash & mask;
  152.         if (containsKey(key, index)) {
  153.             return values[index];
  154.         }

  155.         if (states[index] == FREE) {
  156.             return missingEntries;
  157.         }

  158.         int j = index;
  159.         for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
  160.             j = probe(perturb, j);
  161.             index = j & mask;
  162.             if (containsKey(key, index)) {
  163.                 return values[index];
  164.             }
  165.         }

  166.         return missingEntries;
  167.     }

  168.     /**
  169.      * Check if a value is associated with a key.
  170.      * @param key key to check
  171.      * @return true if a value is associated with key
  172.      */
  173.     public boolean containsKey(final int key) {

  174.         final int hash  = hashOf(key);
  175.         int index = hash & mask;
  176.         if (containsKey(key, index)) {
  177.             return true;
  178.         }

  179.         if (states[index] == FREE) {
  180.             return false;
  181.         }

  182.         int j = index;
  183.         for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
  184.             j = probe(perturb, j);
  185.             index = j & mask;
  186.             if (containsKey(key, index)) {
  187.                 return true;
  188.             }
  189.         }

  190.         return false;
  191.     }

  192.     /**
  193.      * Get an iterator over map elements.
  194.      * <p>The specialized iterators returned are fail-fast: they throw a
  195.      * <code>ConcurrentModificationException</code> when they detect the map
  196.      * has been modified during iteration.</p>
  197.      * @return iterator over the map elements
  198.      */
  199.     public Iterator iterator() {
  200.         return new Iterator();
  201.     }

  202.     /**
  203.      * Perturb the hash for starting probing.
  204.      * @param hash initial hash
  205.      * @return perturbed hash
  206.      */
  207.     private static int perturb(final int hash) {
  208.         return hash & 0x7fffffff;
  209.     }

  210.     /**
  211.      * Find the index at which a key should be inserted.
  212.      * @param key key to lookup
  213.      * @return index at which key should be inserted
  214.      */
  215.     private int findInsertionIndex(final int key) {
  216.         return findInsertionIndex(keys, states, key, mask);
  217.     }

  218.     /**
  219.      * Find the index at which a key should be inserted.
  220.      * @param keys keys table
  221.      * @param states states table
  222.      * @param key key to lookup
  223.      * @param mask bit mask for hash values
  224.      * @return index at which key should be inserted
  225.      */
  226.     private static int findInsertionIndex(final int[] keys, final byte[] states,
  227.                                           final int key, final int mask) {
  228.         final int hash = hashOf(key);
  229.         int index = hash & mask;
  230.         if (states[index] == FREE) {
  231.             return index;
  232.         } else if (states[index] == FULL && keys[index] == key) {
  233.             return changeIndexSign(index);
  234.         }

  235.         int perturb = perturb(hash);
  236.         int j = index;
  237.         if (states[index] == FULL) {
  238.             while (true) {
  239.                 j = probe(perturb, j);
  240.                 index = j & mask;
  241.                 perturb >>= PERTURB_SHIFT;

  242.                 if (states[index] != FULL || keys[index] == key) {
  243.                     break;
  244.                 }
  245.             }
  246.         }

  247.         if (states[index] == FREE) {
  248.             return index;
  249.         } else if (states[index] == FULL) {
  250.             // due to the loop exit condition,
  251.             // if (states[index] == FULL) then keys[index] == key
  252.             return changeIndexSign(index);
  253.         }

  254.         final int firstRemoved = index;
  255.         while (true) {
  256.             j = probe(perturb, j);
  257.             index = j & mask;

  258.             if (states[index] == FREE) {
  259.                 return firstRemoved;
  260.             } else if (states[index] == FULL && keys[index] == key) {
  261.                 return changeIndexSign(index);
  262.             }

  263.             perturb >>= PERTURB_SHIFT;
  264.         }
  265.     }

  266.     /**
  267.      * Compute next probe for collision resolution.
  268.      * @param perturb perturbed hash
  269.      * @param j previous probe
  270.      * @return next probe
  271.      */
  272.     private static int probe(final int perturb, final int j) {
  273.         return (j << 2) + j + perturb + 1;
  274.     }

  275.     /**
  276.      * Change the index sign.
  277.      * @param index initial index
  278.      * @return changed index
  279.      */
  280.     private static int changeIndexSign(final int index) {
  281.         return -index - 1;
  282.     }

  283.     /**
  284.      * Get the number of elements stored in the map.
  285.      * @return number of elements stored in the map
  286.      */
  287.     public int size() {
  288.         return size;
  289.     }


  290.     /**
  291.      * Remove the value associated with a key.
  292.      * @param key key to which the value is associated
  293.      * @return removed value
  294.      */
  295.     public double remove(final int key) {

  296.         final int hash  = hashOf(key);
  297.         int index = hash & mask;
  298.         if (containsKey(key, index)) {
  299.             return doRemove(index);
  300.         }

  301.         if (states[index] == FREE) {
  302.             return missingEntries;
  303.         }

  304.         int j = index;
  305.         for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
  306.             j = probe(perturb, j);
  307.             index = j & mask;
  308.             if (containsKey(key, index)) {
  309.                 return doRemove(index);
  310.             }
  311.         }

  312.         return missingEntries;
  313.     }

  314.     /**
  315.      * Check if the tables contain an element associated with specified key
  316.      * at specified index.
  317.      * @param key key to check
  318.      * @param index index to check
  319.      * @return true if an element is associated with key at index
  320.      */
  321.     private boolean containsKey(final int key, final int index) {
  322.         return (key != 0 || states[index] == FULL) && keys[index] == key;
  323.     }

  324.     /**
  325.      * Remove an element at specified index.
  326.      * @param index index of the element to remove
  327.      * @return removed value
  328.      */
  329.     private double doRemove(int index) {
  330.         keys[index]   = 0;
  331.         states[index] = REMOVED;
  332.         final double previous = values[index];
  333.         values[index] = missingEntries;
  334.         --size;
  335.         ++count;
  336.         return previous;
  337.     }

  338.     /**
  339.      * Put a value associated with a key in the map.
  340.      * @param key key to which value is associated
  341.      * @param value value to put in the map
  342.      * @return previous value associated with the key
  343.      */
  344.     public double put(final int key, final double value) {
  345.         int index = findInsertionIndex(key);
  346.         double previous = missingEntries;
  347.         boolean newMapping = true;
  348.         if (index < 0) {
  349.             index = changeIndexSign(index);
  350.             previous = values[index];
  351.             newMapping = false;
  352.         }
  353.         keys[index]   = key;
  354.         states[index] = FULL;
  355.         values[index] = value;
  356.         if (newMapping) {
  357.             ++size;
  358.             if (shouldGrowTable()) {
  359.                 growTable();
  360.             }
  361.             ++count;
  362.         }
  363.         return previous;
  364.     }

  365.     /**
  366.      * Grow the tables.
  367.      */
  368.     private void growTable() {

  369.         final int oldLength      = states.length;
  370.         final int[] oldKeys      = keys;
  371.         final double[] oldValues = values;
  372.         final byte[] oldStates   = states;

  373.         final int newLength = RESIZE_MULTIPLIER * oldLength;
  374.         final int[] newKeys = new int[newLength];
  375.         final double[] newValues = new double[newLength];
  376.         final byte[] newStates = new byte[newLength];
  377.         final int newMask = newLength - 1;
  378.         for (int i = 0; i < oldLength; ++i) {
  379.             if (oldStates[i] == FULL) {
  380.                 final int key = oldKeys[i];
  381.                 final int index = findInsertionIndex(newKeys, newStates, key, newMask);
  382.                 newKeys[index]   = key;
  383.                 newValues[index] = oldValues[i];
  384.                 newStates[index] = FULL;
  385.             }
  386.         }

  387.         mask   = newMask;
  388.         keys   = newKeys;
  389.         values = newValues;
  390.         states = newStates;
  391.     }

  392.     /**
  393.      * Check if tables should grow due to increased size.
  394.      * @return true if  tables should grow
  395.      */
  396.     private boolean shouldGrowTable() {
  397.         return size > (mask + 1) * LOAD_FACTOR;
  398.     }

  399.     /**
  400.      * Compute the hash value of a key.
  401.      * @param key key to hash
  402.      * @return hash value of the key
  403.      */
  404.     private static int hashOf(final int key) {
  405.         final int h = key ^ ((key >>> 20) ^ (key >>> 12));
  406.         return h ^ (h >>> 7) ^ (h >>> 4);
  407.     }


  408.     /** Iterator class for the map. */
  409.     public final class Iterator {

  410.         /** Reference modification count. */
  411.         private final int referenceCount;

  412.         /** Index of current element. */
  413.         private int current;

  414.         /** Index of next element. */
  415.         private int next;

  416.         /**
  417.          * Simple constructor.
  418.          */
  419.         private Iterator() {

  420.             // preserve the modification count of the map to detect concurrent modifications later
  421.             referenceCount = count;

  422.             // initialize current index
  423.             next = -1;
  424.             try {
  425.                 advance();
  426.             } catch (NoSuchElementException nsee) { // NOPMD
  427.                 // ignored
  428.             }
  429.         }

  430.         /**
  431.          * Check if there is a next element in the map.
  432.          * @return true if there is a next element
  433.          */
  434.         public boolean hasNext() {
  435.             return next >= 0;
  436.         }

  437.         /**
  438.          * Get the key of current entry.
  439.          * @return key of current entry
  440.          * @exception ConcurrentModificationException if the map is modified during iteration
  441.          * @exception NoSuchElementException if there is no element left in the map
  442.          */
  443.         public int key()
  444.             throws ConcurrentModificationException, NoSuchElementException {
  445.             if (referenceCount != count) {
  446.                 throw new ConcurrentModificationException();
  447.             }
  448.             if (current < 0) {
  449.                 throw new NoSuchElementException();
  450.             }
  451.             return keys[current];
  452.         }

  453.         /**
  454.          * Get the value of current entry.
  455.          * @return value of current entry
  456.          * @exception ConcurrentModificationException if the map is modified during iteration
  457.          * @exception NoSuchElementException if there is no element left in the map
  458.          */
  459.         public double value()
  460.             throws ConcurrentModificationException, NoSuchElementException {
  461.             if (referenceCount != count) {
  462.                 throw new ConcurrentModificationException();
  463.             }
  464.             if (current < 0) {
  465.                 throw new NoSuchElementException();
  466.             }
  467.             return values[current];
  468.         }

  469.         /**
  470.          * Advance iterator one step further.
  471.          * @exception ConcurrentModificationException if the map is modified during iteration
  472.          * @exception NoSuchElementException if there is no element left in the map
  473.          */
  474.         public void advance()
  475.             throws ConcurrentModificationException, NoSuchElementException {

  476.             if (referenceCount != count) {
  477.                 throw new ConcurrentModificationException();
  478.             }

  479.             // advance on step
  480.             current = next;

  481.             // prepare next step
  482.             try {
  483.                 do {
  484.                     ++next;
  485.                 } while (states[next] != FULL);
  486.             } catch (ArrayIndexOutOfBoundsException e) {
  487.                 next = -2;
  488.                 if (current < 0) {
  489.                     throw new NoSuchElementException();
  490.                 }
  491.             }
  492.         }
  493.     }

  494.     /**
  495.      * Read a serialized object.
  496.      * @param stream input stream
  497.      * @throws IOException if object cannot be read
  498.      * @throws ClassNotFoundException if the class corresponding
  499.      * to the serialized object cannot be found
  500.      */
  501.     private void readObject(final ObjectInputStream stream)
  502.         throws IOException, ClassNotFoundException {
  503.         stream.defaultReadObject();
  504.         count = 0;
  505.     }

  506. }