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