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