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