Home | History | Annotate | Download | only in util
      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.math.util;
     18 
     19 import java.io.IOException;
     20 import java.io.ObjectInputStream;
     21 import java.io.Serializable;
     22 import java.lang.reflect.Array;
     23 import java.util.ConcurrentModificationException;
     24 import java.util.NoSuchElementException;
     25 
     26 import org.apache.commons.math.Field;
     27 import org.apache.commons.math.FieldElement;
     28 import org.apache.commons.math.MathRuntimeException;
     29 import org.apache.commons.math.exception.util.LocalizedFormats;
     30 
     31 /**
     32  * Open addressed map from int to FieldElement.
     33  * <p>This class provides a dedicated map from integers to FieldElements with a
     34  * much smaller memory overhead than standard <code>java.util.Map</code>.</p>
     35  * <p>This class is not synchronized. The specialized iterators returned by
     36  * {@link #iterator()} are fail-fast: they throw a
     37  * <code>ConcurrentModificationException</code> when they detect the map has been
     38  * modified during iteration.</p>
     39  * @param <T> the type of the field elements
     40  * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 aot 2010) $
     41  * @since 2.0
     42  */
     43 public class OpenIntToFieldHashMap<T extends FieldElement<T>> implements Serializable {
     44 
     45     /** Status indicator for free table entries. */
     46     protected static final byte FREE    = 0;
     47 
     48     /** Status indicator for full table entries. */
     49     protected static final byte FULL    = 1;
     50 
     51     /** Status indicator for removed table entries. */
     52     protected static final byte REMOVED = 2;
     53 
     54     /** Serializable version identifier. */
     55     private static final long serialVersionUID = -9179080286849120720L;
     56 
     57     /** Load factor for the map. */
     58     private static final float LOAD_FACTOR = 0.5f;
     59 
     60     /** Default starting size.
     61      * <p>This must be a power of two for bit mask to work properly. </p>
     62      */
     63     private static final int DEFAULT_EXPECTED_SIZE = 16;
     64 
     65     /** Multiplier for size growth when map fills up.
     66      * <p>This must be a power of two for bit mask to work properly. </p>
     67      */
     68     private static final int RESIZE_MULTIPLIER = 2;
     69 
     70     /** Number of bits to perturb the index when probing for collision resolution. */
     71     private static final int PERTURB_SHIFT = 5;
     72 
     73     /** Field to which the elements belong. */
     74     private final Field<T> field;
     75 
     76     /** Keys table. */
     77     private int[] keys;
     78 
     79     /** Values table. */
     80     private T[] values;
     81 
     82     /** States table. */
     83     private byte[] states;
     84 
     85     /** Return value for missing entries. */
     86     private final T missingEntries;
     87 
     88     /** Current size of the map. */
     89     private int size;
     90 
     91     /** Bit mask for hash values. */
     92     private int mask;
     93 
     94     /** Modifications count. */
     95     private transient int count;
     96 
     97     /**
     98      * Build an empty map with default size and using zero for missing entries.
     99      * @param field field to which the elements belong
    100      */
    101     public OpenIntToFieldHashMap(final Field<T>field) {
    102         this(field, DEFAULT_EXPECTED_SIZE, field.getZero());
    103     }
    104 
    105     /**
    106      * Build an empty map with default size
    107      * @param field field to which the elements belong
    108      * @param missingEntries value to return when a missing entry is fetched
    109      */
    110     public OpenIntToFieldHashMap(final Field<T>field, final T missingEntries) {
    111         this(field,DEFAULT_EXPECTED_SIZE, missingEntries);
    112     }
    113 
    114     /**
    115      * Build an empty map with specified size and using zero for missing entries.
    116      * @param field field to which the elements belong
    117      * @param expectedSize expected number of elements in the map
    118      */
    119     public OpenIntToFieldHashMap(final Field<T> field,final int expectedSize) {
    120         this(field,expectedSize, field.getZero());
    121     }
    122 
    123     /**
    124      * Build an empty map with specified size.
    125      * @param field field to which the elements belong
    126      * @param expectedSize expected number of elements in the map
    127      * @param missingEntries value to return when a missing entry is fetched
    128      */
    129     public OpenIntToFieldHashMap(final Field<T> field,final int expectedSize,
    130                                   final T missingEntries) {
    131         this.field = field;
    132         final int capacity = computeCapacity(expectedSize);
    133         keys   = new int[capacity];
    134         values = buildArray(capacity);
    135         states = new byte[capacity];
    136         this.missingEntries = missingEntries;
    137         mask   = capacity - 1;
    138     }
    139 
    140     /**
    141      * Copy constructor.
    142      * @param source map to copy
    143      */
    144     public OpenIntToFieldHashMap(final OpenIntToFieldHashMap<T> source) {
    145         field = source.field;
    146         final int length = source.keys.length;
    147         keys = new int[length];
    148         System.arraycopy(source.keys, 0, keys, 0, length);
    149         values = buildArray(length);
    150         System.arraycopy(source.values, 0, values, 0, length);
    151         states = new byte[length];
    152         System.arraycopy(source.states, 0, states, 0, length);
    153         missingEntries = source.missingEntries;
    154         size  = source.size;
    155         mask  = source.mask;
    156         count = source.count;
    157     }
    158 
    159     /**
    160      * Compute the capacity needed for a given size.
    161      * @param expectedSize expected size of the map
    162      * @return capacity to use for the specified size
    163      */
    164     private static int computeCapacity(final int expectedSize) {
    165         if (expectedSize == 0) {
    166             return 1;
    167         }
    168         final int capacity   = (int) FastMath.ceil(expectedSize / LOAD_FACTOR);
    169         final int powerOfTwo = Integer.highestOneBit(capacity);
    170         if (powerOfTwo == capacity) {
    171             return capacity;
    172         }
    173         return nextPowerOfTwo(capacity);
    174     }
    175 
    176     /**
    177      * Find the smallest power of two greater than the input value
    178      * @param i input value
    179      * @return smallest power of two greater than the input value
    180      */
    181     private static int nextPowerOfTwo(final int i) {
    182         return Integer.highestOneBit(i) << 1;
    183     }
    184 
    185     /**
    186      * Get the stored value associated with the given key
    187      * @param key key associated with the data
    188      * @return data associated with the key
    189      */
    190     public T get(final int key) {
    191 
    192         final int hash  = hashOf(key);
    193         int index = hash & mask;
    194         if (containsKey(key, index)) {
    195             return values[index];
    196         }
    197 
    198         if (states[index] == FREE) {
    199             return missingEntries;
    200         }
    201 
    202         int j = index;
    203         for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
    204             j = probe(perturb, j);
    205             index = j & mask;
    206             if (containsKey(key, index)) {
    207                 return values[index];
    208             }
    209         }
    210 
    211         return missingEntries;
    212 
    213     }
    214 
    215     /**
    216      * Check if a value is associated with a key.
    217      * @param key key to check
    218      * @return true if a value is associated with key
    219      */
    220     public boolean containsKey(final int key) {
    221 
    222         final int hash  = hashOf(key);
    223         int index = hash & mask;
    224         if (containsKey(key, index)) {
    225             return true;
    226         }
    227 
    228         if (states[index] == FREE) {
    229             return false;
    230         }
    231 
    232         int j = index;
    233         for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
    234             j = probe(perturb, j);
    235             index = j & mask;
    236             if (containsKey(key, index)) {
    237                 return true;
    238             }
    239         }
    240 
    241         return false;
    242 
    243     }
    244 
    245     /**
    246      * Get an iterator over map elements.
    247      * <p>The specialized iterators returned are fail-fast: they throw a
    248      * <code>ConcurrentModificationException</code> when they detect the map
    249      * has been modified during iteration.</p>
    250      * @return iterator over the map elements
    251      */
    252     public Iterator iterator() {
    253         return new Iterator();
    254     }
    255 
    256     /**
    257      * Perturb the hash for starting probing.
    258      * @param hash initial hash
    259      * @return perturbed hash
    260      */
    261     private static int perturb(final int hash) {
    262         return hash & 0x7fffffff;
    263     }
    264 
    265     /**
    266      * Find the index at which a key should be inserted
    267      * @param key key to lookup
    268      * @return index at which key should be inserted
    269      */
    270     private int findInsertionIndex(final int key) {
    271         return findInsertionIndex(keys, states, key, mask);
    272     }
    273 
    274     /**
    275      * Find the index at which a key should be inserted
    276      * @param keys keys table
    277      * @param states states table
    278      * @param key key to lookup
    279      * @param mask bit mask for hash values
    280      * @return index at which key should be inserted
    281      */
    282     private static int findInsertionIndex(final int[] keys, final byte[] states,
    283                                           final int key, final int mask) {
    284         final int hash = hashOf(key);
    285         int index = hash & mask;
    286         if (states[index] == FREE) {
    287             return index;
    288         } else if (states[index] == FULL && keys[index] == key) {
    289             return changeIndexSign(index);
    290         }
    291 
    292         int perturb = perturb(hash);
    293         int j = index;
    294         if (states[index] == FULL) {
    295             while (true) {
    296                 j = probe(perturb, j);
    297                 index = j & mask;
    298                 perturb >>= PERTURB_SHIFT;
    299 
    300                 if (states[index] != FULL || keys[index] == key) {
    301                     break;
    302                 }
    303             }
    304         }
    305 
    306         if (states[index] == FREE) {
    307             return index;
    308         } else if (states[index] == FULL) {
    309             // due to the loop exit condition,
    310             // if (states[index] == FULL) then keys[index] == key
    311             return changeIndexSign(index);
    312         }
    313 
    314         final int firstRemoved = index;
    315         while (true) {
    316             j = probe(perturb, j);
    317             index = j & mask;
    318 
    319             if (states[index] == FREE) {
    320                 return firstRemoved;
    321             } else if (states[index] == FULL && keys[index] == key) {
    322                 return changeIndexSign(index);
    323             }
    324 
    325             perturb >>= PERTURB_SHIFT;
    326 
    327         }
    328 
    329     }
    330 
    331     /**
    332      * Compute next probe for collision resolution
    333      * @param perturb perturbed hash
    334      * @param j previous probe
    335      * @return next probe
    336      */
    337     private static int probe(final int perturb, final int j) {
    338         return (j << 2) + j + perturb + 1;
    339     }
    340 
    341     /**
    342      * Change the index sign
    343      * @param index initial index
    344      * @return changed index
    345      */
    346     private static int changeIndexSign(final int index) {
    347         return -index - 1;
    348     }
    349 
    350     /**
    351      * Get the number of elements stored in the map.
    352      * @return number of elements stored in the map
    353      */
    354     public int size() {
    355         return size;
    356     }
    357 
    358 
    359     /**
    360      * Remove the value associated with a key.
    361      * @param key key to which the value is associated
    362      * @return removed value
    363      */
    364     public T remove(final int key) {
    365 
    366         final int hash  = hashOf(key);
    367         int index = hash & mask;
    368         if (containsKey(key, index)) {
    369             return doRemove(index);
    370         }
    371 
    372         if (states[index] == FREE) {
    373             return missingEntries;
    374         }
    375 
    376         int j = index;
    377         for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
    378             j = probe(perturb, j);
    379             index = j & mask;
    380             if (containsKey(key, index)) {
    381                 return doRemove(index);
    382             }
    383         }
    384 
    385         return missingEntries;
    386 
    387     }
    388 
    389     /**
    390      * Check if the tables contain an element associated with specified key
    391      * at specified index.
    392      * @param key key to check
    393      * @param index index to check
    394      * @return true if an element is associated with key at index
    395      */
    396     private boolean containsKey(final int key, final int index) {
    397         return (key != 0 || states[index] == FULL) && keys[index] == key;
    398     }
    399 
    400     /**
    401      * Remove an element at specified index.
    402      * @param index index of the element to remove
    403      * @return removed value
    404      */
    405     private T doRemove(int index) {
    406         keys[index]   = 0;
    407         states[index] = REMOVED;
    408         final T previous = values[index];
    409         values[index] = missingEntries;
    410         --size;
    411         ++count;
    412         return previous;
    413     }
    414 
    415     /**
    416      * Put a value associated with a key in the map.
    417      * @param key key to which value is associated
    418      * @param value value to put in the map
    419      * @return previous value associated with the key
    420      */
    421     public T put(final int key, final T value) {
    422         int index = findInsertionIndex(key);
    423         T previous = missingEntries;
    424         boolean newMapping = true;
    425         if (index < 0) {
    426             index = changeIndexSign(index);
    427             previous = values[index];
    428             newMapping = false;
    429         }
    430         keys[index]   = key;
    431         states[index] = FULL;
    432         values[index] = value;
    433         if (newMapping) {
    434             ++size;
    435             if (shouldGrowTable()) {
    436                 growTable();
    437             }
    438             ++count;
    439         }
    440         return previous;
    441 
    442     }
    443 
    444     /**
    445      * Grow the tables.
    446      */
    447     private void growTable() {
    448 
    449         final int oldLength      = states.length;
    450         final int[] oldKeys      = keys;
    451         final T[] oldValues = values;
    452         final byte[] oldStates   = states;
    453 
    454         final int newLength = RESIZE_MULTIPLIER * oldLength;
    455         final int[] newKeys = new int[newLength];
    456         final T[] newValues = buildArray(newLength);
    457         final byte[] newStates = new byte[newLength];
    458         final int newMask = newLength - 1;
    459         for (int i = 0; i < oldLength; ++i) {
    460             if (oldStates[i] == FULL) {
    461                 final int key = oldKeys[i];
    462                 final int index = findInsertionIndex(newKeys, newStates, key, newMask);
    463                 newKeys[index]   = key;
    464                 newValues[index] = oldValues[i];
    465                 newStates[index] = FULL;
    466             }
    467         }
    468 
    469         mask   = newMask;
    470         keys   = newKeys;
    471         values = newValues;
    472         states = newStates;
    473 
    474     }
    475 
    476     /**
    477      * Check if tables should grow due to increased size.
    478      * @return true if  tables should grow
    479      */
    480     private boolean shouldGrowTable() {
    481         return size > (mask + 1) * LOAD_FACTOR;
    482     }
    483 
    484     /**
    485      * Compute the hash value of a key
    486      * @param key key to hash
    487      * @return hash value of the key
    488      */
    489     private static int hashOf(final int key) {
    490         final int h = key ^ ((key >>> 20) ^ (key >>> 12));
    491         return h ^ (h >>> 7) ^ (h >>> 4);
    492     }
    493 
    494 
    495     /** Iterator class for the map. */
    496     public class Iterator {
    497 
    498         /** Reference modification count. */
    499         private final int referenceCount;
    500 
    501         /** Index of current element. */
    502         private int current;
    503 
    504         /** Index of next element. */
    505         private int next;
    506 
    507         /**
    508          * Simple constructor.
    509          */
    510         private Iterator() {
    511 
    512             // preserve the modification count of the map to detect concurrent modifications later
    513             referenceCount = count;
    514 
    515             // initialize current index
    516             next = -1;
    517             try {
    518                 advance();
    519             } catch (NoSuchElementException nsee) {
    520                 // ignored
    521             }
    522 
    523         }
    524 
    525         /**
    526          * Check if there is a next element in the map.
    527          * @return true if there is a next element
    528          */
    529         public boolean hasNext() {
    530             return next >= 0;
    531         }
    532 
    533         /**
    534          * Get the key of current entry.
    535          * @return key of current entry
    536          * @exception ConcurrentModificationException if the map is modified during iteration
    537          * @exception NoSuchElementException if there is no element left in the map
    538          */
    539         public int key()
    540             throws ConcurrentModificationException, NoSuchElementException {
    541             if (referenceCount != count) {
    542                 throw MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
    543             }
    544             if (current < 0) {
    545                 throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
    546             }
    547             return keys[current];
    548         }
    549 
    550         /**
    551          * Get the value of current entry.
    552          * @return value of current entry
    553          * @exception ConcurrentModificationException if the map is modified during iteration
    554          * @exception NoSuchElementException if there is no element left in the map
    555          */
    556         public T value()
    557             throws ConcurrentModificationException, NoSuchElementException {
    558             if (referenceCount != count) {
    559                 throw MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
    560             }
    561             if (current < 0) {
    562                 throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
    563             }
    564             return values[current];
    565         }
    566 
    567         /**
    568          * Advance iterator one step further.
    569          * @exception ConcurrentModificationException if the map is modified during iteration
    570          * @exception NoSuchElementException if there is no element left in the map
    571          */
    572         public void advance()
    573             throws ConcurrentModificationException, NoSuchElementException {
    574 
    575             if (referenceCount != count) {
    576                 throw MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
    577             }
    578 
    579             // advance on step
    580             current = next;
    581 
    582             // prepare next step
    583             try {
    584                 while (states[++next] != FULL) {
    585                     // nothing to do
    586                 }
    587             } catch (ArrayIndexOutOfBoundsException e) {
    588                 next = -2;
    589                 if (current < 0) {
    590                     throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
    591                 }
    592             }
    593 
    594         }
    595 
    596     }
    597 
    598     /**
    599      * Read a serialized object.
    600      * @param stream input stream
    601      * @throws IOException if object cannot be read
    602      * @throws ClassNotFoundException if the class corresponding
    603      * to the serialized object cannot be found
    604      */
    605     private void readObject(final ObjectInputStream stream)
    606         throws IOException, ClassNotFoundException {
    607         stream.defaultReadObject();
    608         count = 0;
    609     }
    610 
    611     /** Build an array of elements.
    612      * @param length size of the array to build
    613      * @return a new array
    614      */
    615     @SuppressWarnings("unchecked") // field is of type T
    616     private T[] buildArray(final int length) {
    617         return (T[]) Array.newInstance(field.getZero().getClass(), length);
    618     }
    619 
    620 }
    621