source: src/main/java/agents/org/apache/commons/math/util/OpenIntToDoubleHashMap.java

Last change on this file was 1, checked in by Wouter Pasman, 7 years ago

Initial import : Genius 9.0.0

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