All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hipparchus.util.ResizableDoubleArray Maven / Gradle / Ivy

There is a newer version: 3.1
Show newest version
/*
 * 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
 *
 *      http://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.Serializable;
import java.util.Arrays;

import org.hipparchus.exception.LocalizedCoreFormats;
import org.hipparchus.exception.MathIllegalArgumentException;
import org.hipparchus.exception.MathIllegalStateException;
import org.hipparchus.exception.NullArgumentException;

/**
 * A variable length primitive double array implementation that automatically
 * handles expanding and contracting its internal storage array as elements
 * are added and removed.
 * 

* The internal storage array starts with capacity determined by the * {@code initialCapacity} property, which can be set by the constructor. * The default initial capacity is 16. Adding elements using * {@link #addElement(double)} appends elements to the end of the array. * When there are no open entries at the end of the internal storage array, * the array is expanded. The size of the expanded array depends on the * {@code expansionMode} and {@code expansionFactor} properties. * The {@code expansionMode} determines whether the size of the array is * multiplied by the {@code expansionFactor} * ({@link ExpansionMode#MULTIPLICATIVE}) or if the expansion is additive * ({@link ExpansionMode#ADDITIVE} -- {@code expansionFactor} storage * locations added). * The default {@code expansionMode} is {@code MULTIPLICATIVE} and the default * {@code expansionFactor} is 2. *

* The {@link #addElementRolling(double)} method adds a new element to the end * of the internal storage array and adjusts the "usable window" of the * internal array forward by one position (effectively making what was the * second element the first, and so on). Repeated activations of this method * (or activation of {@link #discardFrontElements(int)}) will effectively orphan * the storage locations at the beginning of the internal storage array. To * reclaim this storage, each time one of these methods is activated, the size * of the internal storage array is compared to the number of addressable * elements (the {@code numElements} property) and if the difference * is too large, the internal array is contracted to size * {@code numElements + 1}. The determination of when the internal * storage array is "too large" depends on the {@code expansionMode} and * {@code contractionFactor} properties. If the {@code expansionMode} * is {@code MULTIPLICATIVE}, contraction is triggered when the * ratio between storage array length and {@code numElements} exceeds * {@code contractionFactor.} If the {@code expansionMode} * is {@code ADDITIVE}, the number of excess storage locations * is compared to {@code contractionFactor}. *

* To avoid cycles of expansions and contractions, the * {@code expansionFactor} must not exceed the {@code contractionFactor}. * Constructors and mutators for both of these properties enforce this * requirement, throwing a {@code MathIllegalArgumentException} if it is * violated. *

* Note: this class is NOT thread-safe. */ public class ResizableDoubleArray implements Serializable { /** Serializable version identifier. */ private static final long serialVersionUID = 20160327L; /** Default value for initial capacity. */ private static final int DEFAULT_INITIAL_CAPACITY = 16; /** Default value for array size modifier. */ private static final double DEFAULT_EXPANSION_FACTOR = 2.0; /** Default value for expansion mode. */ private static final ExpansionMode DEFAULT_EXPANSION_MODE = ExpansionMode.MULTIPLICATIVE; /** * Default value for the difference between {@link #contractionCriterion} * and {@link #expansionFactor}. */ private static final double DEFAULT_CONTRACTION_DELTA = 0.5; /** * The contraction criteria determines when the internal array will be * contracted to fit the number of elements contained in the element * array + 1. */ private final double contractionCriterion; /** * The expansion factor of the array. When the array needs to be expanded, * the new array size will be {@code internalArray.length * expansionFactor} * if {@code expansionMode} is set to MULTIPLICATIVE, or * {@code internalArray.length + expansionFactor} if * {@code expansionMode} is set to ADDITIVE. */ private final double expansionFactor; /** * Determines whether array expansion by {@code expansionFactor} * is additive or multiplicative. */ private final ExpansionMode expansionMode; /** * The internal storage array. */ private double[] internalArray; /** * The number of addressable elements in the array. Note that this * has nothing to do with the length of the internal storage array. */ private int numElements; /** * The position of the first addressable element in the internal storage * array. The addressable elements in the array are * {@code internalArray[startIndex],...,internalArray[startIndex + numElements - 1]}. */ private int startIndex; /** Specification of expansion algorithm. */ public enum ExpansionMode { /** Multiplicative expansion mode. */ MULTIPLICATIVE, /** Additive expansion mode. */ ADDITIVE } /** * Creates an instance with default properties. *

    *
  • {@code initialCapacity = 16}
  • *
  • {@code expansionMode = MULTIPLICATIVE}
  • *
  • {@code expansionFactor = 2.0}
  • *
  • {@code contractionCriterion = 2.5}
  • *
*/ public ResizableDoubleArray() { this(DEFAULT_INITIAL_CAPACITY); } /** * Creates an instance with the specified initial capacity. *

* Other properties take default values: *

    *
  • {@code expansionMode = MULTIPLICATIVE}
  • *
  • {@code expansionFactor = 2.0}
  • *
  • {@code contractionCriterion = 2.5}
  • *
* @param initialCapacity Initial size of the internal storage array. * @throws MathIllegalArgumentException if {@code initialCapacity <= 0}. */ public ResizableDoubleArray(int initialCapacity) throws MathIllegalArgumentException { this(initialCapacity, DEFAULT_EXPANSION_FACTOR); } /** * Creates an instance from an existing {@code double[]} with the * initial capacity and numElements corresponding to the size of * the supplied {@code double[]} array. *

* If the supplied array is null, a new empty array with the default * initial capacity will be created. * The input array is copied, not referenced. * Other properties take default values: *

    *
  • {@code expansionMode = MULTIPLICATIVE}
  • *
  • {@code expansionFactor = 2.0}
  • *
  • {@code contractionCriterion = 2.5}
  • *
* * @param initialArray initial array */ public ResizableDoubleArray(double[] initialArray) { this(initialArray == null || initialArray.length == 0 ? DEFAULT_INITIAL_CAPACITY : initialArray.length, DEFAULT_EXPANSION_FACTOR, DEFAULT_CONTRACTION_DELTA + DEFAULT_EXPANSION_FACTOR, DEFAULT_EXPANSION_MODE, initialArray); } /** * Creates an instance with the specified initial capacity * and expansion factor. *

* The remaining properties take default values: *

    *
  • {@code expansionMode = MULTIPLICATIVE}
  • *
  • {@code contractionCriterion = 0.5 + expansionFactor}
  • *
*

* Throws MathIllegalArgumentException if the following conditions * are not met: *

    *
  • {@code initialCapacity > 0}
  • *
  • {@code expansionFactor > 1}
  • *
* * @param initialCapacity Initial size of the internal storage array. * @param expansionFactor The array will be expanded based on this parameter. * @throws MathIllegalArgumentException if parameters are not valid. */ public ResizableDoubleArray(int initialCapacity, double expansionFactor) throws MathIllegalArgumentException { this(initialCapacity, expansionFactor, DEFAULT_CONTRACTION_DELTA + expansionFactor); } /** * Creates an instance with the specified initial capacity, * expansion factor, and contraction criteria. *

* The expansion mode will default to {@code MULTIPLICATIVE}. *

* Throws MathIllegalArgumentException if the following conditions * are not met: *

    *
  • {@code initialCapacity > 0}
  • *
  • {@code expansionFactor > 1}
  • *
  • {@code contractionCriterion >= expansionFactor}
  • *
* * @param initialCapacity Initial size of the internal storage array. * @param expansionFactor The array will be expanded based on this parameter. * @param contractionCriterion Contraction criterion. * @throws MathIllegalArgumentException if the parameters are not valid. */ public ResizableDoubleArray(int initialCapacity, double expansionFactor, double contractionCriterion) throws MathIllegalArgumentException { this(initialCapacity, expansionFactor, contractionCriterion, DEFAULT_EXPANSION_MODE, null); } /** * Creates an instance with the specified properties. *
* Throws MathIllegalArgumentException if the following conditions * are not met: *
    *
  • {@code initialCapacity > 0}
  • *
  • {@code expansionFactor > 1}
  • *
  • {@code contractionCriterion >= expansionFactor}
  • *
* * @param initialCapacity Initial size of the internal storage array. * @param expansionFactor The array will be expanded based on this parameter. * @param contractionCriterion Contraction criteria. * @param expansionMode Expansion mode. * @param data Initial contents of the array. * @throws MathIllegalArgumentException if the parameters are not valid. * @throws NullArgumentException if expansionMode is null */ public ResizableDoubleArray(int initialCapacity, double expansionFactor, double contractionCriterion, ExpansionMode expansionMode, double ... data) throws MathIllegalArgumentException { if (initialCapacity <= 0) { throw new MathIllegalArgumentException(LocalizedCoreFormats.INITIAL_CAPACITY_NOT_POSITIVE, initialCapacity); } checkContractExpand(contractionCriterion, expansionFactor); MathUtils.checkNotNull(expansionMode); this.expansionFactor = expansionFactor; this.contractionCriterion = contractionCriterion; this.expansionMode = expansionMode; internalArray = new double[initialCapacity]; numElements = 0; startIndex = 0; if (data != null && data.length > 0) { addElements(data); } } /** * Copy constructor. *

* Creates a new ResizableDoubleArray that is a deep, fresh copy of the original. * Original may not be null; otherwise a {@link NullArgumentException} is thrown. * * @param original array to copy * @exception NullArgumentException if original is null */ public ResizableDoubleArray(final ResizableDoubleArray original) throws NullArgumentException { MathUtils.checkNotNull(original); this.contractionCriterion = original.contractionCriterion; this.expansionFactor = original.expansionFactor; this.expansionMode = original.expansionMode; this.internalArray = new double[original.internalArray.length]; System.arraycopy(original.internalArray, 0, this.internalArray, 0, this.internalArray.length); this.numElements = original.numElements; this.startIndex = original.startIndex; } /** * Adds an element to the end of this expandable array. * * @param value Value to be added to end of array. */ public void addElement(final double value) { if (internalArray.length <= startIndex + numElements) { expand(); } internalArray[startIndex + numElements++] = value; } /** * Adds several element to the end of this expandable array. * * @param values Values to be added to end of array. */ public void addElements(final double[] values) { final double[] tempArray = new double[numElements + values.length + 1]; System.arraycopy(internalArray, startIndex, tempArray, 0, numElements); System.arraycopy(values, 0, tempArray, numElements, values.length); internalArray = tempArray; startIndex = 0; numElements += values.length; } /** * Adds an element to the end of the array and removes the first * element in the array. Returns the discarded first element. *

* The effect is similar to a push operation in a FIFO queue. *

* Example: If the array contains the elements 1, 2, 3, 4 (in that order) * and addElementRolling(5) is invoked, the result is an array containing * the entries 2, 3, 4, 5 and the value returned is 1. * * @param value Value to be added to the array. * @return the value which has been discarded or "pushed" out of the array * by this rolling insert. */ public double addElementRolling(double value) { double discarded = internalArray[startIndex]; if ((startIndex + (numElements + 1)) > internalArray.length) { expand(); } // Increment the start index startIndex += 1; // Add the new value internalArray[startIndex + (numElements - 1)] = value; // Check the contraction criterion. if (shouldContract()) { contract(); } return discarded; } /** * Substitutes {@code value} for the most recently added value. *

* Returns the value that has been replaced. If the array is empty (i.e. * if {@link #numElements} is zero), an MathIllegalStateException is thrown. * * @param value New value to substitute for the most recently added value * @return the value that has been replaced in the array. * @throws MathIllegalStateException if the array is empty */ public double substituteMostRecentElement(double value) throws MathIllegalStateException { if (numElements < 1) { throw new MathIllegalStateException(LocalizedCoreFormats.CANNOT_SUBSTITUTE_ELEMENT_FROM_EMPTY_ARRAY); } final int substIndex = startIndex + (numElements - 1); final double discarded = internalArray[substIndex]; internalArray[substIndex] = value; return discarded; } /** * Checks the expansion factor and the contraction criterion and raises * an exception if the contraction criterion is smaller than the * expansion criterion. * * @param contraction Criterion to be checked. * @param expansion Factor to be checked. * @throws MathIllegalArgumentException if {@code contraction < expansion}. * @throws MathIllegalArgumentException if {@code contraction <= 1}. * @throws MathIllegalArgumentException if {@code expansion <= 1 }. */ protected void checkContractExpand(double contraction, double expansion) throws MathIllegalArgumentException { if (contraction < expansion) { throw new MathIllegalArgumentException(LocalizedCoreFormats.CONTRACTION_CRITERIA_SMALLER_THAN_EXPANSION_FACTOR, contraction, expansion); } if (contraction <= 1) { throw new MathIllegalArgumentException(LocalizedCoreFormats.CONTRACTION_CRITERIA_SMALLER_THAN_ONE, contraction); } if (expansion <= 1) { throw new MathIllegalArgumentException(LocalizedCoreFormats.EXPANSION_FACTOR_SMALLER_THAN_ONE, expansion); } } /** * Clear the array contents, resetting the number of elements to zero. */ public void clear() { numElements = 0; startIndex = 0; } /** * Contracts the storage array to the (size of the element set) + 1 - to avoid * a zero length array. This function also resets the startIndex to zero. */ public void contract() { final double[] tempArray = new double[numElements + 1]; // Copy and swap - copy only the element array from the src array. System.arraycopy(internalArray, startIndex, tempArray, 0, numElements); internalArray = tempArray; // Reset the start index to zero startIndex = 0; } /** * Discards the {@code i} initial elements of the array. *

* For example, if the array contains the elements 1,2,3,4, invoking * {@code discardFrontElements(2)} will cause the first two elements * to be discarded, leaving 3,4 in the array. * * @param i the number of elements to discard from the front of the array * @throws MathIllegalArgumentException if i is greater than numElements. */ public void discardFrontElements(int i) throws MathIllegalArgumentException { discardExtremeElements(i,true); } /** * Discards the {@code i} last elements of the array. *

* For example, if the array contains the elements 1,2,3,4, invoking * {@code discardMostRecentElements(2)} will cause the last two elements * to be discarded, leaving 1,2 in the array. * * @param i the number of elements to discard from the end of the array * @throws MathIllegalArgumentException if i is greater than numElements. */ public void discardMostRecentElements(int i) throws MathIllegalArgumentException { discardExtremeElements(i,false); } /** * Discards the {@code i} first or last elements of the array, * depending on the value of {@code front}. *

* For example, if the array contains the elements 1,2,3,4, invoking * {@code discardExtremeElements(2,false)} will cause the last two elements * to be discarded, leaving 1,2 in the array. * For example, if the array contains the elements 1,2,3,4, invoking * {@code discardExtremeElements(2,true)} will cause the first two elements * to be discarded, leaving 3,4 in the array. * * @param i the number of elements to discard from the front/end of the array * @param front true if elements are to be discarded from the front * of the array, false if elements are to be discarded from the end * of the array * @throws MathIllegalArgumentException if i is greater than numElements. */ private void discardExtremeElements(int i, boolean front) throws MathIllegalArgumentException { if (i > numElements) { throw new MathIllegalArgumentException( LocalizedCoreFormats.TOO_MANY_ELEMENTS_TO_DISCARD_FROM_ARRAY, i, numElements); } else if (i < 0) { throw new MathIllegalArgumentException( LocalizedCoreFormats.CANNOT_DISCARD_NEGATIVE_NUMBER_OF_ELEMENTS, i); } else { // "Subtract" this number of discarded from numElements numElements -= i; if (front) { startIndex += i; } } if (shouldContract()) { contract(); } } /** * Expands the internal storage array using the expansion factor. *

* If {@code expansionMode} is set to MULTIPLICATIVE, * the new array size will be {@code internalArray.length * expansionFactor}. * If {@code expansionMode} is set to ADDITIVE, the length * after expansion will be {@code internalArray.length + expansionFactor}. */ protected void expand() { // notice the use of FastMath.ceil(), this guarantees that we will always // have an array of at least currentSize + 1. Assume that the // current initial capacity is 1 and the expansion factor // is 1.000000000000000001. The newly calculated size will be // rounded up to 2 after the multiplication is performed. int newSize = 0; if (expansionMode == ExpansionMode.MULTIPLICATIVE) { newSize = (int) FastMath.ceil(internalArray.length * expansionFactor); } else { newSize = (int) (internalArray.length + FastMath.round(expansionFactor)); } final double[] tempArray = new double[newSize]; // Copy and swap System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length); internalArray = tempArray; } /** * Expands the internal storage array to the specified size. * * @param size Size of the new internal storage array. */ private void expandTo(int size) { final double[] tempArray = new double[size]; // Copy and swap System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length); internalArray = tempArray; } /** * The contraction criterion defines when the internal array will contract * to store only the number of elements in the element array. *

* If the {@code expansionMode} is {@code MULTIPLICATIVE}, * contraction is triggered when the ratio between storage array length * and {@code numElements} exceeds {@code contractionFactor}. * If the {@code expansionMode} is {@code ADDITIVE}, the * number of excess storage locations is compared to {@code contractionFactor}. * * @return the contraction criterion used to reclaim memory. */ public double getContractionCriterion() { return contractionCriterion; } /** * Returns the element at the specified index. * * @param index index to fetch a value from * @return value stored at the specified index * @throws ArrayIndexOutOfBoundsException if {@code index} is less than * zero or is greater than {@code getNumElements() - 1}. */ public double getElement(int index) { if (index >= numElements) { throw new ArrayIndexOutOfBoundsException(index); } else if (index >= 0) { return internalArray[startIndex + index]; } else { throw new ArrayIndexOutOfBoundsException(index); } } /** * Returns a double array containing the elements of this ResizableArray. *

* This method returns a copy, not a reference to the underlying array, * so that changes made to the returned array have no effect on this ResizableArray. * * @return the double array. */ public double[] getElements() { final double[] elementArray = new double[numElements]; System.arraycopy(internalArray, startIndex, elementArray, 0, numElements); return elementArray; } /** * The expansion factor controls the size of a new array when an array * needs to be expanded. *

* The {@code expansionMode} determines whether the size of the array * is multiplied by the {@code expansionFactor} (MULTIPLICATIVE) or if * the expansion is additive (ADDITIVE -- {@code expansionFactor} * storage locations added). The default {@code expansionMode} is * MULTIPLICATIVE and the default {@code expansionFactor} is 2.0. * * @return the expansion factor of this expandable double array */ public double getExpansionFactor() { return expansionFactor; } /** * The expansion mode determines whether the internal storage * array grows additively or multiplicatively when it is expanded. * * @return the expansion mode. */ public ExpansionMode getExpansionMode() { return expansionMode; } /** * Gets the currently allocated size of the internal data structure used * for storing elements. * This is not to be confused with {@link #getNumElements() the number of * elements actually stored}. * * @return the length of the internal array. */ public int getCapacity() { return internalArray.length; } /** * Returns the number of elements currently in the array. Please note * that this is different from the length of the internal storage array. * * @return the number of elements. */ public int getNumElements() { return numElements; } /** * Provides direct access to the internal storage array. * Please note that this method returns a reference to this object's * storage array, not a copy. *

* To correctly address elements of the array, the "start index" is * required (available via the {@link #getStartIndex() getStartIndex} * method. *

* This method should only be used to avoid copying the internal array. * The returned value must be used for reading only; other * uses could lead to this object becoming inconsistent. *

* The {@link #getElements} method has no such limitation since it * returns a copy of this array's addressable elements. * * @return the internal storage array used by this object. */ protected double[] getArrayRef() { return internalArray; // NOPMD - returning an internal array is intentional and documented here } /** * Returns the "start index" of the internal array. * This index is the position of the first addressable element in the * internal storage array. *

* The addressable elements in the array are at indices contained in * the interval [{@link #getStartIndex()}, * {@link #getStartIndex()} + {@link #getNumElements()} - 1]. * * @return the start index. */ protected int getStartIndex() { return startIndex; } /** * Performs an operation on the addressable elements of the array. * * @param f Function to be applied on this array. * @return the result. */ public double compute(MathArrays.Function f) { return f.evaluate(internalArray, startIndex, numElements); } /** * Sets the element at the specified index. *

* If the specified index is greater than {@code getNumElements() - 1}, * the {@code numElements} property is increased to {@code index +1} * and additional storage is allocated (if necessary) for the new element and * all (uninitialized) elements between the new element and the previous end * of the array). * * @param index index to store a value in * @param value value to store at the specified index * @throws ArrayIndexOutOfBoundsException if {@code index < 0}. */ public void setElement(int index, double value) { if (index < 0) { throw new ArrayIndexOutOfBoundsException(index); } if (index + 1 > numElements) { numElements = index + 1; } if ((startIndex + index) >= internalArray.length) { expandTo(startIndex + (index + 1)); } internalArray[startIndex + index] = value; } /** * This function allows you to control the number of elements contained * in this array, and can be used to "throw out" the last n values in an * array. This function will also expand the internal array as needed. * * @param i a new number of elements * @throws MathIllegalArgumentException if {@code i} is negative. */ public void setNumElements(int i) throws MathIllegalArgumentException { // If index is negative thrown an error. if (i < 0) { throw new MathIllegalArgumentException(LocalizedCoreFormats.INDEX_NOT_POSITIVE, i); } // Test the new num elements, check to see if the array needs to be // expanded to accommodate this new number of elements. final int newSize = startIndex + i; if (newSize > internalArray.length) { expandTo(newSize); } // Set the new number of elements to new value. numElements = i; } /** * Returns true if the internal storage array has too many unused * storage positions. * * @return true if array satisfies the contraction criteria */ private boolean shouldContract() { if (expansionMode == ExpansionMode.MULTIPLICATIVE) { return (internalArray.length / ((float) numElements)) > contractionCriterion; } else { return (internalArray.length - numElements) > contractionCriterion; } } /** * Returns a copy of the ResizableDoubleArray. Does not contract before * the copy, so the returned object is an exact copy of this. * * @return a new ResizableDoubleArray with the same data and configuration * properties as this */ public ResizableDoubleArray copy() { return new ResizableDoubleArray(this); } /** * Returns true iff object is a ResizableDoubleArray with the same properties * as this and an identical internal storage array. * * @param object object to be compared for equality with this * @return true iff object is a ResizableDoubleArray with the same data and * properties as this */ @Override public boolean equals(Object object) { if (object == this) { return true; } if (!(object instanceof ResizableDoubleArray)) { return false; } boolean result = true; final ResizableDoubleArray other = (ResizableDoubleArray) object; result = result && (other.contractionCriterion == contractionCriterion); result = result && (other.expansionFactor == expansionFactor); result = result && (other.expansionMode == expansionMode); result = result && (other.numElements == numElements); result = result && (other.startIndex == startIndex); if (!result) { return false; } else { return Arrays.equals(internalArray, other.internalArray); } } /** * Returns a hash code consistent with equals. * * @return the hash code representing this {@code ResizableDoubleArray}. */ @Override public int hashCode() { final int[] hashData = new int[6]; hashData[0] = Double.valueOf(expansionFactor).hashCode(); hashData[1] = Double.valueOf(contractionCriterion).hashCode(); hashData[2] = expansionMode.hashCode(); hashData[3] = Arrays.hashCode(internalArray); hashData[4] = numElements; hashData[5] = startIndex; return Arrays.hashCode(hashData); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy