OpenIntToDoubleHashMap.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * This is not the original file distributed by the Apache Software Foundation
 * It has been modified by the Hipparchus project
 */

package org.hipparchus.util;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.NoSuchElementException;

/**
 * Open addressed map from int to double.
 * <p>This class provides a dedicated map from integers to doubles with a
 * much smaller memory overhead than standard <code>java.util.Map</code>.</p>
 * <p>This class is not synchronized. The specialized iterators returned by
 * {@link #iterator()} are fail-fast: they throw a
 * <code>ConcurrentModificationException</code> when they detect the map has been
 * modified during iteration.</p>
 */
public class OpenIntToDoubleHashMap extends AbstractOpenIntHashMap implements Serializable {

    /** Serializable version identifier */
    private static final long serialVersionUID = 20240326L;

    /** Values table. */
    private double[] values;

    /** Return value for missing entries. */
    private final double missingEntries;

    /**
     * Build an empty map with default size and using NaN for missing entries.
     */
    public OpenIntToDoubleHashMap() {
        this(DEFAULT_EXPECTED_SIZE, Double.NaN);
    }

    /**
     * Build an empty map with default size
     * @param missingEntries value to return when a missing entry is fetched
     */
    public OpenIntToDoubleHashMap(final double missingEntries) {
        this(DEFAULT_EXPECTED_SIZE, missingEntries);
    }

    /**
     * Build an empty map with specified size and using NaN for missing entries.
     * @param expectedSize expected number of elements in the map
     */
    public OpenIntToDoubleHashMap(final int expectedSize) {
        this(expectedSize, Double.NaN);
    }

    /**
     * Build an empty map with specified size.
     * @param expectedSize expected number of elements in the map
     * @param missingEntries value to return when a missing entry is fetched
     */
    public OpenIntToDoubleHashMap(final int expectedSize,
                                  final double missingEntries) {
        super(expectedSize);
        values = new double[getCapacity()];
        this.missingEntries = missingEntries;
    }

    /**
     * Copy constructor.
     * @param source map to copy
     */
    public OpenIntToDoubleHashMap(final OpenIntToDoubleHashMap source) {
        super(source);
        values = new double[getCapacity()];
        System.arraycopy(source.values, 0, values, 0, getCapacity());
        missingEntries = source.missingEntries;
    }

    /**
     * Get the stored value associated with the given key
     * @param key key associated with the data
     * @return data associated with the key
     */
    public double get(final int key) {
        final int index = locate(key);
        return index < 0 ? missingEntries : values[index];
    }

    /**
     * Get an iterator over map elements.
     * <p>The specialized iterators returned are fail-fast: they throw a
     * <code>ConcurrentModificationException</code> when they detect the map
     * has been modified during iteration.</p>
     * @return iterator over the map elements
     */
    public Iterator iterator() {
        return new Iterator();
    }

    /** {@inheritDoc} */
    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        final OpenIntToDoubleHashMap that = (OpenIntToDoubleHashMap) o;
        return equalKeys(that) && equalStates(that) && Arrays.equals(values, that.values);
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode() {
        return keysStatesHashCode() + Arrays.hashCode(values);
    }

    /**
     * Remove the value associated with a key.
     * @param key key to which the value is associated
     * @return removed value
     */
    public double remove(final int key) {
        final int index = locate(key);
        if (index < 0) {
            return missingEntries;
        } else {
            final double previous = values[index];
            doRemove(index);
            values[index] = missingEntries;
            return previous;
        }
    }

    /**
     * Put a value associated with a key in the map.
     * @param key key to which value is associated
     * @param value value to put in the map
     * @return previous value associated with the key
     */
    public double put(final int key, final double value) {
        final InsertionHolder ih = put(key);
        final double previous = ih.isExisting() ? values[ih.getIndex()] : missingEntries;
        values[ih.getIndex()] = value;
        return previous;
    }

    /** {@inheritDoc} */
    @Override
    protected int growTable(final int oldIndex) {
        final double[] newValues = new double[RESIZE_MULTIPLIER * values.length];
        final int      newIndex  = doGrowTable(oldIndex, (src, dest) -> newValues[dest] = values[src]);
        values = newValues;
        return newIndex;
    }

    /** Iterator class for the map. */
    public class Iterator extends BaseIterator {

        /** Get the value of current entry.
         * @return value of current entry
         * @exception ConcurrentModificationException if the map is modified during iteration
         * @exception NoSuchElementException if there is no element left in the map
         */
        public double value() throws ConcurrentModificationException, NoSuchElementException {
            return values[getCurrent()];
        }

    }

    /**
     * Read a serialized object.
     * @param stream input stream
     * @throws IOException if object cannot be read
     * @throws ClassNotFoundException if the class corresponding
     * to the serialized object cannot be found
     */
    private void readObject(final ObjectInputStream stream)
        throws IOException, ClassNotFoundException {
        stream.defaultReadObject();
        resetCount();
    }

    /**
     * Replace the instance with a data transfer object for serialization.
     * @return data transfer object that will be serialized
     */
    private Object writeReplace() {
        return new DataTransferObject(missingEntries, getSize(), iterator());
    }

    /** Internal class used only for serialization. */
    private static class DataTransferObject implements Serializable {

        /** Serializable UID. */
        private static final long serialVersionUID = 20240326L;

        /** Return value for missing entries. */
        private final double missingEntries;

        /** Keys table. */
        private final int[] keys;

        /** Values table. */
        private final double[] values;

        /** Simple constructor.
         * @param missingEntries return value for missing entries
         * @param size number of objects in the map
         * @param iterator iterator on serialized map
         */
        DataTransferObject(final double missingEntries, final int size, final Iterator iterator) {
            this.missingEntries = missingEntries;
            this.keys           = new int[size];
            this.values         = new double[size];
            for (int i = 0; i < size; ++i) {
                iterator.advance();
                keys[i]   = iterator.key();
                values[i] = iterator.value();
            }
        }

        /** Replace the deserialized data transfer object with a {@link OpenIntToDoubleHashMap}.
         * @return replacement {@link OpenIntToDoubleHashMap}
         */
        private Object readResolve() {
            final OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap(missingEntries);
            for (int i = 0; i < keys.length; ++i) {
                map.put(keys[i], values[i]);
            }
            return map;
        }

    }

}