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