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