001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.math3.util;
019
020 import java.io.IOException;
021 import java.io.ObjectInputStream;
022 import java.io.Serializable;
023 import java.util.ConcurrentModificationException;
024 import java.util.NoSuchElementException;
025
026 /**
027 * Open addressed map from int to double.
028 * <p>This class provides a dedicated map from integers to doubles with a
029 * much smaller memory overhead than standard <code>java.util.Map</code>.</p>
030 * <p>This class is not synchronized. The specialized iterators returned by
031 * {@link #iterator()} are fail-fast: they throw a
032 * <code>ConcurrentModificationException</code> when they detect the map has been
033 * modified during iteration.</p>
034 * @version $Id: OpenIntToDoubleHashMap.java 1421448 2012-12-13 19:45:57Z tn $
035 * @since 2.0
036 */
037 public class OpenIntToDoubleHashMap implements Serializable {
038
039 /** Status indicator for free table entries. */
040 protected static final byte FREE = 0;
041
042 /** Status indicator for full table entries. */
043 protected static final byte FULL = 1;
044
045 /** Status indicator for removed table entries. */
046 protected static final byte REMOVED = 2;
047
048 /** Serializable version identifier */
049 private static final long serialVersionUID = -3646337053166149105L;
050
051 /** Load factor for the map. */
052 private static final float LOAD_FACTOR = 0.5f;
053
054 /** Default starting size.
055 * <p>This must be a power of two for bit mask to work properly. </p>
056 */
057 private static final int DEFAULT_EXPECTED_SIZE = 16;
058
059 /** Multiplier for size growth when map fills up.
060 * <p>This must be a power of two for bit mask to work properly. </p>
061 */
062 private static final int RESIZE_MULTIPLIER = 2;
063
064 /** Number of bits to perturb the index when probing for collision resolution. */
065 private static final int PERTURB_SHIFT = 5;
066
067 /** Keys table. */
068 private int[] keys;
069
070 /** Values table. */
071 private double[] values;
072
073 /** States table. */
074 private byte[] states;
075
076 /** Return value for missing entries. */
077 private final double missingEntries;
078
079 /** Current size of the map. */
080 private int size;
081
082 /** Bit mask for hash values. */
083 private int mask;
084
085 /** Modifications count. */
086 private transient int count;
087
088 /**
089 * Build an empty map with default size and using NaN for missing entries.
090 */
091 public OpenIntToDoubleHashMap() {
092 this(DEFAULT_EXPECTED_SIZE, Double.NaN);
093 }
094
095 /**
096 * Build an empty map with default size
097 * @param missingEntries value to return when a missing entry is fetched
098 */
099 public OpenIntToDoubleHashMap(final double missingEntries) {
100 this(DEFAULT_EXPECTED_SIZE, missingEntries);
101 }
102
103 /**
104 * Build an empty map with specified size and using NaN for missing entries.
105 * @param expectedSize expected number of elements in the map
106 */
107 public OpenIntToDoubleHashMap(final int expectedSize) {
108 this(expectedSize, Double.NaN);
109 }
110
111 /**
112 * Build an empty map with specified size.
113 * @param expectedSize expected number of elements in the map
114 * @param missingEntries value to return when a missing entry is fetched
115 */
116 public OpenIntToDoubleHashMap(final int expectedSize,
117 final double missingEntries) {
118 final int capacity = computeCapacity(expectedSize);
119 keys = new int[capacity];
120 values = new double[capacity];
121 states = new byte[capacity];
122 this.missingEntries = missingEntries;
123 mask = capacity - 1;
124 }
125
126 /**
127 * Copy constructor.
128 * @param source map to copy
129 */
130 public OpenIntToDoubleHashMap(final OpenIntToDoubleHashMap source) {
131 final int length = source.keys.length;
132 keys = new int[length];
133 System.arraycopy(source.keys, 0, keys, 0, length);
134 values = new double[length];
135 System.arraycopy(source.values, 0, values, 0, length);
136 states = new byte[length];
137 System.arraycopy(source.states, 0, states, 0, length);
138 missingEntries = source.missingEntries;
139 size = source.size;
140 mask = source.mask;
141 count = source.count;
142 }
143
144 /**
145 * Compute the capacity needed for a given size.
146 * @param expectedSize expected size of the map
147 * @return capacity to use for the specified size
148 */
149 private static int computeCapacity(final int expectedSize) {
150 if (expectedSize == 0) {
151 return 1;
152 }
153 final int capacity = (int) FastMath.ceil(expectedSize / LOAD_FACTOR);
154 final int powerOfTwo = Integer.highestOneBit(capacity);
155 if (powerOfTwo == capacity) {
156 return capacity;
157 }
158 return nextPowerOfTwo(capacity);
159 }
160
161 /**
162 * Find the smallest power of two greater than the input value
163 * @param i input value
164 * @return smallest power of two greater than the input value
165 */
166 private static int nextPowerOfTwo(final int i) {
167 return Integer.highestOneBit(i) << 1;
168 }
169
170 /**
171 * Get the stored value associated with the given key
172 * @param key key associated with the data
173 * @return data associated with the key
174 */
175 public double get(final int key) {
176
177 final int hash = hashOf(key);
178 int index = hash & mask;
179 if (containsKey(key, index)) {
180 return values[index];
181 }
182
183 if (states[index] == FREE) {
184 return missingEntries;
185 }
186
187 int j = index;
188 for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
189 j = probe(perturb, j);
190 index = j & mask;
191 if (containsKey(key, index)) {
192 return values[index];
193 }
194 }
195
196 return missingEntries;
197
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 /**
231 * Get an iterator over map elements.
232 * <p>The specialized iterators returned are fail-fast: they throw a
233 * <code>ConcurrentModificationException</code> when they detect the map
234 * has been modified during iteration.</p>
235 * @return iterator over the map elements
236 */
237 public Iterator iterator() {
238 return new Iterator();
239 }
240
241 /**
242 * Perturb the hash for starting probing.
243 * @param hash initial hash
244 * @return perturbed hash
245 */
246 private static int perturb(final int hash) {
247 return hash & 0x7fffffff;
248 }
249
250 /**
251 * Find the index at which a key should be inserted
252 * @param key key to lookup
253 * @return index at which key should be inserted
254 */
255 private int findInsertionIndex(final int key) {
256 return findInsertionIndex(keys, states, key, mask);
257 }
258
259 /**
260 * Find the index at which a key should be inserted
261 * @param keys keys table
262 * @param states states table
263 * @param key key to lookup
264 * @param mask bit mask for hash values
265 * @return index at which key should be inserted
266 */
267 private static int findInsertionIndex(final int[] keys, final byte[] states,
268 final int key, final int mask) {
269 final int hash = hashOf(key);
270 int index = hash & mask;
271 if (states[index] == FREE) {
272 return index;
273 } else if (states[index] == FULL && keys[index] == key) {
274 return changeIndexSign(index);
275 }
276
277 int perturb = perturb(hash);
278 int j = index;
279 if (states[index] == FULL) {
280 while (true) {
281 j = probe(perturb, j);
282 index = j & mask;
283 perturb >>= PERTURB_SHIFT;
284
285 if (states[index] != FULL || keys[index] == key) {
286 break;
287 }
288 }
289 }
290
291 if (states[index] == FREE) {
292 return index;
293 } else if (states[index] == FULL) {
294 // due to the loop exit condition,
295 // if (states[index] == FULL) then keys[index] == key
296 return changeIndexSign(index);
297 }
298
299 final int firstRemoved = index;
300 while (true) {
301 j = probe(perturb, j);
302 index = j & mask;
303
304 if (states[index] == FREE) {
305 return firstRemoved;
306 } else if (states[index] == FULL && keys[index] == key) {
307 return changeIndexSign(index);
308 }
309
310 perturb >>= PERTURB_SHIFT;
311
312 }
313
314 }
315
316 /**
317 * Compute next probe for collision resolution
318 * @param perturb perturbed hash
319 * @param j previous probe
320 * @return next probe
321 */
322 private static int probe(final int perturb, final int j) {
323 return (j << 2) + j + perturb + 1;
324 }
325
326 /**
327 * Change the index sign
328 * @param index initial index
329 * @return changed index
330 */
331 private static int changeIndexSign(final int index) {
332 return -index - 1;
333 }
334
335 /**
336 * Get the number of elements stored in the map.
337 * @return number of elements stored in the map
338 */
339 public int size() {
340 return size;
341 }
342
343
344 /**
345 * Remove the value associated with a key.
346 * @param key key to which the value is associated
347 * @return removed value
348 */
349 public double remove(final int key) {
350
351 final int hash = hashOf(key);
352 int index = hash & mask;
353 if (containsKey(key, index)) {
354 return doRemove(index);
355 }
356
357 if (states[index] == FREE) {
358 return missingEntries;
359 }
360
361 int j = index;
362 for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
363 j = probe(perturb, j);
364 index = j & mask;
365 if (containsKey(key, index)) {
366 return doRemove(index);
367 }
368 }
369
370 return missingEntries;
371
372 }
373
374 /**
375 * Check if the tables contain an element associated with specified key
376 * at specified index.
377 * @param key key to check
378 * @param index index to check
379 * @return true if an element is associated with key at index
380 */
381 private boolean containsKey(final int key, final int index) {
382 return (key != 0 || states[index] == FULL) && keys[index] == key;
383 }
384
385 /**
386 * Remove an element at specified index.
387 * @param index index of the element to remove
388 * @return removed value
389 */
390 private double doRemove(int index) {
391 keys[index] = 0;
392 states[index] = REMOVED;
393 final double previous = values[index];
394 values[index] = missingEntries;
395 --size;
396 ++count;
397 return previous;
398 }
399
400 /**
401 * Put a value associated with a key in the map.
402 * @param key key to which value is associated
403 * @param value value to put in the map
404 * @return previous value associated with the key
405 */
406 public double put(final int key, final double value) {
407 int index = findInsertionIndex(key);
408 double previous = missingEntries;
409 boolean newMapping = true;
410 if (index < 0) {
411 index = changeIndexSign(index);
412 previous = values[index];
413 newMapping = false;
414 }
415 keys[index] = key;
416 states[index] = FULL;
417 values[index] = value;
418 if (newMapping) {
419 ++size;
420 if (shouldGrowTable()) {
421 growTable();
422 }
423 ++count;
424 }
425 return previous;
426
427 }
428
429 /**
430 * Grow the tables.
431 */
432 private void growTable() {
433
434 final int oldLength = states.length;
435 final int[] oldKeys = keys;
436 final double[] oldValues = values;
437 final byte[] oldStates = states;
438
439 final int newLength = RESIZE_MULTIPLIER * oldLength;
440 final int[] newKeys = new int[newLength];
441 final double[] newValues = new double[newLength];
442 final byte[] newStates = new byte[newLength];
443 final int newMask = newLength - 1;
444 for (int i = 0; i < oldLength; ++i) {
445 if (oldStates[i] == FULL) {
446 final int key = oldKeys[i];
447 final int index = findInsertionIndex(newKeys, newStates, key, newMask);
448 newKeys[index] = key;
449 newValues[index] = oldValues[i];
450 newStates[index] = FULL;
451 }
452 }
453
454 mask = newMask;
455 keys = newKeys;
456 values = newValues;
457 states = newStates;
458
459 }
460
461 /**
462 * Check if tables should grow due to increased size.
463 * @return true if tables should grow
464 */
465 private boolean shouldGrowTable() {
466 return size > (mask + 1) * LOAD_FACTOR;
467 }
468
469 /**
470 * Compute the hash value of a key
471 * @param key key to hash
472 * @return hash value of the key
473 */
474 private static int hashOf(final int key) {
475 final int h = key ^ ((key >>> 20) ^ (key >>> 12));
476 return h ^ (h >>> 7) ^ (h >>> 4);
477 }
478
479
480 /** Iterator class for the map. */
481 public class Iterator {
482
483 /** Reference modification count. */
484 private final int referenceCount;
485
486 /** Index of current element. */
487 private int current;
488
489 /** Index of next element. */
490 private int next;
491
492 /**
493 * Simple constructor.
494 */
495 private Iterator() {
496
497 // preserve the modification count of the map to detect concurrent modifications later
498 referenceCount = count;
499
500 // initialize current index
501 next = -1;
502 try {
503 advance();
504 } catch (NoSuchElementException nsee) { // NOPMD
505 // ignored
506 }
507
508 }
509
510 /**
511 * Check if there is a next element in the map.
512 * @return true if there is a next element
513 */
514 public boolean hasNext() {
515 return next >= 0;
516 }
517
518 /**
519 * Get the key of current entry.
520 * @return key of current entry
521 * @exception ConcurrentModificationException if the map is modified during iteration
522 * @exception NoSuchElementException if there is no element left in the map
523 */
524 public int key()
525 throws ConcurrentModificationException, NoSuchElementException {
526 if (referenceCount != count) {
527 throw new ConcurrentModificationException();
528 }
529 if (current < 0) {
530 throw new NoSuchElementException();
531 }
532 return keys[current];
533 }
534
535 /**
536 * Get the value of current entry.
537 * @return value of current entry
538 * @exception ConcurrentModificationException if the map is modified during iteration
539 * @exception NoSuchElementException if there is no element left in the map
540 */
541 public double value()
542 throws ConcurrentModificationException, NoSuchElementException {
543 if (referenceCount != count) {
544 throw new ConcurrentModificationException();
545 }
546 if (current < 0) {
547 throw new NoSuchElementException();
548 }
549 return values[current];
550 }
551
552 /**
553 * Advance iterator one step further.
554 * @exception ConcurrentModificationException if the map is modified during iteration
555 * @exception NoSuchElementException if there is no element left in the map
556 */
557 public void advance()
558 throws ConcurrentModificationException, NoSuchElementException {
559
560 if (referenceCount != count) {
561 throw new ConcurrentModificationException();
562 }
563
564 // advance on step
565 current = next;
566
567 // prepare next step
568 try {
569 while (states[++next] != FULL) { // NOPMD
570 // nothing to do
571 }
572 } catch (ArrayIndexOutOfBoundsException e) {
573 next = -2;
574 if (current < 0) {
575 throw new NoSuchElementException();
576 }
577 }
578
579 }
580
581 }
582
583 /**
584 * Read a serialized object.
585 * @param stream input stream
586 * @throws IOException if object cannot be read
587 * @throws ClassNotFoundException if the class corresponding
588 * to the serialized object cannot be found
589 */
590 private void readObject(final ObjectInputStream stream)
591 throws IOException, ClassNotFoundException {
592 stream.defaultReadObject();
593 count = 0;
594 }
595
596
597 }