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