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    }