View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) 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 ASF 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    *      http://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.apache.commons.math4.legacy.stat.descriptive;
18  
19  import java.util.Arrays;
20  
21  import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
22  import org.apache.commons.math4.legacy.exception.MathIllegalStateException;
23  import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
24  import org.apache.commons.math4.legacy.exception.NullArgumentException;
25  import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
26  import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
27  import org.apache.commons.math4.core.jdkmath.JdkMath;
28  import org.apache.commons.math4.legacy.core.MathArrays;
29  
30  /**
31   * A variable length {@link DoubleArray} implementation that automatically
32   * handles expanding and contracting its internal storage array as elements
33   * are added and removed.
34   * <p>
35   * The internal storage array starts with capacity determined by the
36   * {@code initialCapacity} property, which can be set by the constructor.
37   * The default initial capacity is 16.  Adding elements using
38   * {@link #addElement(double)} appends elements to the end of the array.
39   * When there are no open entries at the end of the internal storage array,
40   * the array is expanded.  The size of the expanded array depends on the
41   * {@code expansionMode} and {@code expansionFactor} properties.
42   * The {@code expansionMode} determines whether the size of the array is
43   * multiplied by the {@code expansionFactor}
44   * ({@link ExpansionMode#MULTIPLICATIVE}) or if the expansion is additive
45   * ({@link ExpansionMode#ADDITIVE} -- {@code expansionFactor} storage
46   * locations added).
47   * The default {@code expansionMode} is {@code MULTIPLICATIVE} and the default
48   * {@code expansionFactor} is 2.
49   * <p>
50   * The {@link #addElementRolling(double)} method adds a new element to the end
51   * of the internal storage array and adjusts the "usable window" of the
52   * internal array forward by one position (effectively making what was the
53   * second element the first, and so on).  Repeated activations of this method
54   * (or activation of {@link #discardFrontElements(int)}) will effectively orphan
55   * the storage locations at the beginning of the internal storage array.  To
56   * reclaim this storage, each time one of these methods is activated, the size
57   * of the internal storage array is compared to the number of addressable
58   * elements (the {@code numElements} property) and if the difference
59   * is too large, the internal array is contracted to size
60   * {@code numElements + 1}.  The determination of when the internal
61   * storage array is "too large" depends on the {@code expansionMode} and
62   * {@code contractionFactor} properties.  If  the {@code expansionMode}
63   * is {@code MULTIPLICATIVE}, contraction is triggered when the
64   * ratio between storage array length and {@code numElements} exceeds
65   * {@code contractionFactor.}  If the {@code expansionMode}
66   * is {@code ADDITIVE}, the number of excess storage locations
67   * is compared to {@code contractionFactor}.
68   * <p>
69   * To avoid cycles of expansions and contractions, the
70   * {@code expansionFactor} must not exceed the {@code contractionFactor}.
71   * Constructors and mutators for both of these properties enforce this
72   * requirement, throwing a {@code MathIllegalArgumentException} if it is
73   * violated.
74   * <p>
75   * <b>Note:</b> this class is <b>NOT</b> thread-safe.
76   */
77  class ResizableDoubleArray implements DoubleArray { // Not in public API.
78      /** Default value for initial capacity. */
79      private static final int DEFAULT_INITIAL_CAPACITY = 16;
80      /** Default value for array size modifier. */
81      private static final double DEFAULT_EXPANSION_FACTOR = 2.0;
82      /** Default value for expansion mode. */
83      private static final ExpansionMode DEFAULT_EXPANSION_MODE = ExpansionMode.MULTIPLICATIVE;
84      /**
85       * Default value for the difference between {@link #contractionCriterion}
86       * and {@link #expansionFactor}.
87       */
88      private static final double DEFAULT_CONTRACTION_DELTA = 0.5;
89  
90      /**
91       * The contraction criteria determines when the internal array will be
92       * contracted to fit the number of elements contained in the element
93       *  array + 1.
94       */
95      private final double contractionCriterion;
96  
97      /**
98       * The expansion factor of the array.  When the array needs to be expanded,
99       * the new array size will be {@code internalArray.length * expansionFactor}
100      * if {@code expansionMode} is set to MULTIPLICATIVE, or
101      * {@code internalArray.length + expansionFactor} if
102      * {@code expansionMode} is set to ADDITIVE.
103      */
104     private final double expansionFactor;
105 
106     /**
107      * Determines whether array expansion by {@code expansionFactor}
108      * is additive or multiplicative.
109      */
110     private final ExpansionMode expansionMode;
111 
112     /**
113      * The internal storage array.
114      */
115     private double[] internalArray;
116 
117     /**
118      * The number of addressable elements in the array.  Note that this
119      * has nothing to do with the length of the internal storage array.
120      */
121     private int numElements;
122 
123     /**
124      * The position of the first addressable element in the internal storage
125      * array.  The addressable elements in the array are
126      * {@code internalArray[startIndex],...,internalArray[startIndex + numElements - 1]}.
127      */
128     private int startIndex;
129 
130     /**
131      * Specification of expansion algorithm.
132      * @since 3.1
133      */
134     public enum ExpansionMode {
135         /** Multiplicative expansion mode. */
136         MULTIPLICATIVE,
137         /** Additive expansion mode. */
138         ADDITIVE
139     }
140 
141     /**
142      * Creates an instance with default properties.
143      * <ul>
144      *  <li>{@code initialCapacity = 16}</li>
145      *  <li>{@code expansionMode = MULTIPLICATIVE}</li>
146      *  <li>{@code expansionFactor = 2.0}</li>
147      *  <li>{@code contractionCriterion = 2.5}</li>
148      * </ul>
149      */
150     ResizableDoubleArray() {
151         this(DEFAULT_INITIAL_CAPACITY);
152     }
153 
154     /**
155      * Creates an instance with the specified initial capacity.
156      * <p>
157      * Other properties take default values:
158      * <ul>
159      *  <li>{@code expansionMode = MULTIPLICATIVE}</li>
160      *  <li>{@code expansionFactor = 2.0}</li>
161      *  <li>{@code contractionCriterion = 2.5}</li>
162      * </ul>
163      * @param initialCapacity Initial size of the internal storage array.
164      * @throws MathIllegalArgumentException if {@code initialCapacity <= 0}.
165      */
166     ResizableDoubleArray(int initialCapacity) throws MathIllegalArgumentException {
167         this(initialCapacity, DEFAULT_EXPANSION_FACTOR);
168     }
169 
170     /**
171      * Creates an instance from an existing {@code double[]} with the
172      * initial capacity and numElements corresponding to the size of
173      * the supplied {@code double[]} array.
174      * <p>
175      * If the supplied array is null, a new empty array with the default
176      * initial capacity will be created.
177      * The input array is copied, not referenced.
178      * Other properties take default values:
179      * <ul>
180      *  <li>{@code expansionMode = MULTIPLICATIVE}</li>
181      *  <li>{@code expansionFactor = 2.0}</li>
182      *  <li>{@code contractionCriterion = 2.5}</li>
183      * </ul>
184      *
185      * @param initialArray initial array
186      * @since 2.2
187      */
188     ResizableDoubleArray(double[] initialArray) {
189         this(initialArray == null || initialArray.length == 0 ?
190               DEFAULT_INITIAL_CAPACITY :
191               initialArray.length,
192              DEFAULT_EXPANSION_FACTOR,
193              DEFAULT_CONTRACTION_DELTA + DEFAULT_EXPANSION_FACTOR,
194              DEFAULT_EXPANSION_MODE,
195              initialArray);
196     }
197 
198     /**
199      * Creates an instance with the specified initial capacity
200      * and expansion factor.
201      * <p>
202      * The remaining properties take default values:
203      * <ul>
204      *  <li>{@code expansionMode = MULTIPLICATIVE}</li>
205      *  <li>{@code contractionCriterion = 0.5 + expansionFactor}</li>
206      * </ul>
207      * <p>
208      * Throws MathIllegalArgumentException if the following conditions
209      * are not met:
210      * <ul>
211      *  <li>{@code initialCapacity > 0}</li>
212      *  <li>{@code expansionFactor > 1}</li>
213      * </ul>
214      *
215      * @param initialCapacity Initial size of the internal storage array.
216      * @param expansionFactor The array will be expanded based on this parameter.
217      * @throws MathIllegalArgumentException if parameters are not valid.
218      * @since 3.1
219      */
220     ResizableDoubleArray(int initialCapacity, double expansionFactor) throws MathIllegalArgumentException {
221         this(initialCapacity, expansionFactor, DEFAULT_CONTRACTION_DELTA + expansionFactor);
222     }
223 
224     /**
225      * Creates an instance with the specified initial capacity,
226      * expansion factor, and contraction criteria.
227      * <p>
228      * The expansion mode will default to {@code MULTIPLICATIVE}.
229      * <p>
230      * Throws MathIllegalArgumentException if the following conditions
231      * are not met:
232      * <ul>
233      *  <li>{@code initialCapacity > 0}</li>
234      *  <li>{@code expansionFactor > 1}</li>
235      *  <li>{@code contractionCriterion >= expansionFactor}</li>
236      * </ul>
237      *
238      * @param initialCapacity Initial size of the internal storage array.
239      * @param expansionFactor The array will be expanded based on this parameter.
240      * @param contractionCriterion Contraction criterion.
241      * @throws MathIllegalArgumentException if the parameters are not valid.
242      * @since 3.1
243      */
244     ResizableDoubleArray(int initialCapacity, double expansionFactor, double contractionCriterion)
245         throws MathIllegalArgumentException {
246         this(initialCapacity, expansionFactor, contractionCriterion, DEFAULT_EXPANSION_MODE);
247     }
248 
249     /**
250      * Creates an instance with the specified properties.
251      * <br>
252      * Throws MathIllegalArgumentException if the following conditions
253      * are not met:
254      * <ul>
255      *  <li>{@code initialCapacity > 0}</li>
256      *  <li>{@code expansionFactor > 1}</li>
257      *  <li>{@code contractionCriterion >= expansionFactor}</li>
258      * </ul>
259      *
260      * @param initialCapacity Initial size of the internal storage array.
261      * @param expansionFactor The array will be expanded based on this parameter.
262      * @param contractionCriterion Contraction criteria.
263      * @param expansionMode Expansion mode.
264      * @param data Initial contents of the array.
265      * @throws MathIllegalArgumentException if the parameters are not valid.
266      * @throws NullArgumentException if expansionMode is null
267      */
268     ResizableDoubleArray(int initialCapacity,
269                          double expansionFactor,
270                          double contractionCriterion,
271                          ExpansionMode expansionMode,
272                          double ... data)
273         throws MathIllegalArgumentException {
274         if (initialCapacity <= 0) {
275             throw new NotStrictlyPositiveException(LocalizedFormats.INITIAL_CAPACITY_NOT_POSITIVE,
276                                                    initialCapacity);
277         }
278         checkContractExpand(contractionCriterion, expansionFactor);
279         NullArgumentException.check(expansionMode);
280 
281         this.expansionFactor = expansionFactor;
282         this.contractionCriterion = contractionCriterion;
283         this.expansionMode = expansionMode;
284         internalArray = new double[initialCapacity];
285         numElements = 0;
286         startIndex = 0;
287 
288         if (data != null && data.length > 0) {
289             addElements(data);
290         }
291     }
292 
293     /**
294      * Copy constructor.
295      * <p>
296      * Creates a new ResizableDoubleArray that is a deep, fresh copy of the original.
297      * Original may not be null; otherwise a {@link NullArgumentException} is thrown.
298      *
299      * @param original array to copy
300      * @exception NullArgumentException if original is null
301      * @since 2.0
302      */
303     ResizableDoubleArray(final ResizableDoubleArray original)
304         throws NullArgumentException {
305         NullArgumentException.check(original);
306         this.contractionCriterion = original.contractionCriterion;
307         this.expansionFactor = original.expansionFactor;
308         this.expansionMode = original.expansionMode;
309         this.internalArray = new double[original.internalArray.length];
310         System.arraycopy(original.internalArray, 0, this.internalArray, 0, this.internalArray.length);
311         this.numElements = original.numElements;
312         this.startIndex = original.startIndex;
313     }
314 
315     /**
316      * Adds an element to the end of this expandable array.
317      *
318      * @param value Value to be added to end of array.
319      */
320     @Override
321     public void addElement(final double value) {
322         if (internalArray.length <= startIndex + numElements) {
323             expand();
324         }
325         internalArray[startIndex + numElements++] = value;
326     }
327 
328     /**
329      * Adds several element to the end of this expandable array.
330      *
331      * @param values Values to be added to end of array.
332      * @since 2.2
333      */
334     @Override
335     public void addElements(final double[] values) {
336         final double[] tempArray = new double[numElements + values.length + 1];
337         System.arraycopy(internalArray, startIndex, tempArray, 0, numElements);
338         System.arraycopy(values, 0, tempArray, numElements, values.length);
339         internalArray = tempArray;
340         startIndex = 0;
341         numElements += values.length;
342     }
343 
344     /**
345      * Adds an element to the end of the array and removes the first
346      * element in the array.  Returns the discarded first element.
347      * <p>
348      * The effect is similar to a push operation in a FIFO queue.
349      * <p>
350      * Example: If the array contains the elements 1, 2, 3, 4 (in that order)
351      * and addElementRolling(5) is invoked, the result is an array containing
352      * the entries 2, 3, 4, 5 and the value returned is 1.
353      *
354      * @param value Value to be added to the array.
355      * @return the value which has been discarded or "pushed" out of the array
356      * by this rolling insert.
357      */
358     @Override
359     public double addElementRolling(double value) {
360         double discarded = internalArray[startIndex];
361 
362         if ((startIndex + (numElements + 1)) > internalArray.length) {
363             expand();
364         }
365         // Increment the start index
366         startIndex += 1;
367 
368         // Add the new value
369         internalArray[startIndex + (numElements - 1)] = value;
370 
371         // Check the contraction criterion.
372         if (shouldContract()) {
373             contract();
374         }
375         return discarded;
376     }
377 
378     /**
379      * Substitutes {@code value} for the most recently added value.
380      * <p>
381      * Returns the value that has been replaced. If the array is empty (i.e.
382      * if {@link #numElements} is zero), an MathIllegalStateException is thrown.
383      *
384      * @param value New value to substitute for the most recently added value
385      * @return the value that has been replaced in the array.
386      * @throws MathIllegalStateException if the array is empty
387      * @since 2.0
388      */
389     public double substituteMostRecentElement(double value) throws MathIllegalStateException {
390         if (numElements < 1) {
391             throw new MathIllegalStateException(LocalizedFormats.CANNOT_SUBSTITUTE_ELEMENT_FROM_EMPTY_ARRAY);
392         }
393 
394         final int substIndex = startIndex + (numElements - 1);
395         final double discarded = internalArray[substIndex];
396 
397         internalArray[substIndex] = value;
398 
399         return discarded;
400     }
401 
402     /**
403      * Checks the expansion factor and the contraction criterion and raises
404      * an exception if the contraction criterion is smaller than the
405      * expansion criterion.
406      *
407      * @param contraction Criterion to be checked.
408      * @param expansion Factor to be checked.
409      * @throws NumberIsTooSmallException if {@code contraction < expansion}.
410      * @throws NumberIsTooSmallException if {@code contraction <= 1}.
411      * @throws NumberIsTooSmallException if {@code expansion <= 1 }.
412      * @since 3.1
413      */
414     protected void checkContractExpand(double contraction, double expansion) throws NumberIsTooSmallException {
415         if (contraction < expansion) {
416             final NumberIsTooSmallException e = new NumberIsTooSmallException(contraction, 1, true);
417             e.getContext().addMessage(LocalizedFormats.CONTRACTION_CRITERIA_SMALLER_THAN_EXPANSION_FACTOR,
418                                       contraction, expansion);
419             throw e;
420         }
421 
422         if (contraction <= 1) {
423             final NumberIsTooSmallException e = new NumberIsTooSmallException(contraction, 1, false);
424             e.getContext().addMessage(LocalizedFormats.CONTRACTION_CRITERIA_SMALLER_THAN_ONE,
425                                       contraction);
426             throw e;
427         }
428 
429         if (expansion <= 1) {
430             final NumberIsTooSmallException e = new NumberIsTooSmallException(contraction, 1, false);
431             e.getContext().addMessage(LocalizedFormats.EXPANSION_FACTOR_SMALLER_THAN_ONE,
432                                       expansion);
433             throw e;
434         }
435     }
436 
437     /**
438      * Clear the array contents, resetting the number of elements to zero.
439      */
440     @Override
441     public void clear() {
442         numElements = 0;
443         startIndex = 0;
444     }
445 
446     /**
447      * Contracts the storage array to the (size of the element set) + 1 - to avoid
448      * a zero length array. This function also resets the startIndex to zero.
449      */
450     public void contract() {
451         final double[] tempArray = new double[numElements + 1];
452 
453         // Copy and swap - copy only the element array from the src array.
454         System.arraycopy(internalArray, startIndex, tempArray, 0, numElements);
455         internalArray = tempArray;
456 
457         // Reset the start index to zero
458         startIndex = 0;
459     }
460 
461     /**
462      * Discards the {@code i} initial elements of the array.
463      * <p>
464      * For example, if the array contains the elements 1,2,3,4, invoking
465      * {@code discardFrontElements(2)} will cause the first two elements
466      * to be discarded, leaving 3,4 in the array.
467      *
468      * @param i  the number of elements to discard from the front of the array
469      * @throws MathIllegalArgumentException if i is greater than numElements.
470      * @since 2.0
471      */
472     public void discardFrontElements(int i) throws MathIllegalArgumentException {
473         discardExtremeElements(i,true);
474     }
475 
476     /**
477      * Discards the {@code i} last elements of the array.
478      * <p>
479      * For example, if the array contains the elements 1,2,3,4, invoking
480      * {@code discardMostRecentElements(2)} will cause the last two elements
481      * to be discarded, leaving 1,2 in the array.
482      *
483      * @param i  the number of elements to discard from the end of the array
484      * @throws MathIllegalArgumentException if i is greater than numElements.
485      * @since 2.0
486      */
487     public void discardMostRecentElements(int i) throws MathIllegalArgumentException {
488         discardExtremeElements(i,false);
489     }
490 
491     /**
492      * Discards the {@code i} first or last elements of the array,
493      * depending on the value of {@code front}.
494      * <p>
495      * For example, if the array contains the elements 1,2,3,4, invoking
496      * {@code discardExtremeElements(2,false)} will cause the last two elements
497      * to be discarded, leaving 1,2 in the array.
498      * For example, if the array contains the elements 1,2,3,4, invoking
499      * {@code discardExtremeElements(2,true)} will cause the first two elements
500      * to be discarded, leaving 3,4 in the array.
501      *
502      * @param i  the number of elements to discard from the front/end of the array
503      * @param front true if elements are to be discarded from the front
504      * of the array, false if elements are to be discarded from the end
505      * of the array
506      * @throws MathIllegalArgumentException if i is greater than numElements.
507      * @since 2.0
508      */
509     private void discardExtremeElements(int i, boolean front) throws MathIllegalArgumentException {
510         if (i > numElements) {
511             throw new MathIllegalArgumentException(
512                     LocalizedFormats.TOO_MANY_ELEMENTS_TO_DISCARD_FROM_ARRAY,
513                     i, numElements);
514        } else if (i < 0) {
515            throw new MathIllegalArgumentException(
516                    LocalizedFormats.CANNOT_DISCARD_NEGATIVE_NUMBER_OF_ELEMENTS,
517                    i);
518         } else {
519             // "Subtract" this number of discarded from numElements
520             numElements -= i;
521             if (front) {
522                 startIndex += i;
523             }
524         }
525         if (shouldContract()) {
526             contract();
527         }
528     }
529 
530     /**
531      * Expands the internal storage array using the expansion factor.
532      * <p>
533      * If {@code expansionMode} is set to MULTIPLICATIVE,
534      * the new array size will be {@code internalArray.length * expansionFactor}.
535      * If {@code expansionMode} is set to ADDITIVE, the length
536      * after expansion will be {@code internalArray.length + expansionFactor}.
537      */
538     protected void expand() {
539         // notice the use of JdkMath.ceil(), this guarantees that we will always
540         // have an array of at least currentSize + 1.   Assume that the
541         // current initial capacity is 1 and the expansion factor
542         // is 1.000000000000000001.  The newly calculated size will be
543         // rounded up to 2 after the multiplication is performed.
544         int newSize = 0;
545         if (expansionMode == ExpansionMode.MULTIPLICATIVE) {
546             newSize = (int) JdkMath.ceil(internalArray.length * expansionFactor);
547         } else {
548             newSize = (int) (internalArray.length + JdkMath.round(expansionFactor));
549         }
550         final double[] tempArray = new double[newSize];
551 
552         // Copy and swap
553         System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length);
554         internalArray = tempArray;
555     }
556 
557     /**
558      * Expands the internal storage array to the specified size.
559      *
560      * @param size Size of the new internal storage array.
561      */
562     private void expandTo(int size) {
563         final double[] tempArray = new double[size];
564         // Copy and swap
565         System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length);
566         internalArray = tempArray;
567     }
568 
569     /**
570      * The contraction criterion defines when the internal array will contract
571      * to store only the number of elements in the element array.
572      * <p>
573      * If the {@code expansionMode} is {@code MULTIPLICATIVE},
574      * contraction is triggered when the ratio between storage array length
575      * and {@code numElements} exceeds {@code contractionFactor}.
576      * If the {@code expansionMode} is {@code ADDITIVE}, the
577      * number of excess storage locations is compared to {@code contractionFactor}.
578      *
579      * @return the contraction criterion used to reclaim memory.
580      * @since 3.1
581      */
582     public double getContractionCriterion() {
583         return contractionCriterion;
584     }
585 
586     /**
587      * Returns the element at the specified index.
588      *
589      * @param index index to fetch a value from
590      * @return value stored at the specified index
591      * @throws ArrayIndexOutOfBoundsException if {@code index} is less than
592      * zero or is greater than {@code getNumElements() - 1}.
593      */
594     @Override
595     public double getElement(int index) {
596         if (index >= numElements) {
597             throw new ArrayIndexOutOfBoundsException(index);
598         } else if (index >= 0) {
599             return internalArray[startIndex + index];
600         } else {
601             throw new ArrayIndexOutOfBoundsException(index);
602         }
603     }
604 
605      /**
606      * Returns a double array containing the elements of this ResizableArray.
607      * <p>
608      * This method returns a copy, not a reference to the underlying array,
609      * so that changes made to the returned array have no effect on this ResizableArray.
610      *
611      * @return the double array.
612      */
613     @Override
614     public double[] getElements() {
615         final double[] elementArray = new double[numElements];
616         System.arraycopy(internalArray, startIndex, elementArray, 0, numElements);
617         return elementArray;
618     }
619 
620     /**
621      * The expansion factor controls the size of a new array when an array
622      * needs to be expanded.
623      * <p>
624      * The {@code expansionMode} determines whether the size of the array
625      * is multiplied by the {@code expansionFactor} (MULTIPLICATIVE) or if
626      * the expansion is additive (ADDITIVE -- {@code expansionFactor}
627      * storage locations added).  The default {@code expansionMode} is
628      * MULTIPLICATIVE and the default {@code expansionFactor} is 2.0.
629      *
630      * @return the expansion factor of this expandable double array
631      */
632     public double getExpansionFactor() {
633         return expansionFactor;
634     }
635 
636     /**
637      * The expansion mode determines whether the internal storage
638      * array grows additively or multiplicatively when it is expanded.
639      *
640      * @return the expansion mode.
641      */
642     public ExpansionMode getExpansionMode() {
643         return expansionMode;
644     }
645 
646     /**
647      * Gets the currently allocated size of the internal data structure used
648      * for storing elements.
649      * This is not to be confused with {@link #getNumElements() the number of
650      * elements actually stored}.
651      *
652      * @return the length of the internal array.
653      * @since 3.1
654      */
655     public int getCapacity() {
656         return internalArray.length;
657     }
658 
659     /**
660      * Returns the number of elements currently in the array.  Please note
661      * that this is different from the length of the internal storage array.
662      *
663      * @return the number of elements.
664      */
665     @Override
666     public int getNumElements() {
667         return numElements;
668     }
669 
670     /**
671      * Provides <em>direct</em> access to the internal storage array.
672      * Please note that this method returns a reference to this object's
673      * storage array, not a copy.
674      * <p>
675      * To correctly address elements of the array, the "start index" is
676      * required (available via the {@link #getStartIndex() getStartIndex}
677      * method.
678      * <p>
679      * This method should only be used to avoid copying the internal array.
680      * The returned value <em>must</em> be used for reading only; other
681      * uses could lead to this object becoming inconsistent.
682      * <p>
683      * The {@link #getElements} method has no such limitation since it
684      * returns a copy of this array's addressable elements.
685      *
686      * @return the internal storage array used by this object.
687      * @since 3.1
688      */
689     protected double[] getArrayRef() {
690         return internalArray;
691     }
692 
693     /**
694      * Returns the "start index" of the internal array.
695      * This index is the position of the first addressable element in the
696      * internal storage array.
697      * <p>
698      * The addressable elements in the array are at indices contained in
699      * the interval [{@link #getStartIndex()},
700      *               {@link #getStartIndex()} + {@link #getNumElements()} - 1].
701      *
702      * @return the start index.
703      * @since 3.1
704      */
705     protected int getStartIndex() {
706         return startIndex;
707     }
708 
709     /**
710      * Performs an operation on the addressable elements of the array.
711      *
712      * @param f Function to be applied on this array.
713      * @return the result.
714      * @since 3.1
715      */
716     public double compute(MathArrays.Function f) {
717         return f.evaluate(internalArray, startIndex, numElements);
718     }
719 
720     /**
721      * Sets the element at the specified index.
722      * <p>
723      * If the specified index is greater than {@code getNumElements() - 1},
724      * the {@code numElements} property is increased to {@code index +1}
725      * and additional storage is allocated (if necessary) for the new element and
726      * all (uninitialized) elements between the new element and the previous end
727      * of the array).
728      *
729      * @param index index to store a value in
730      * @param value value to store at the specified index
731      * @throws ArrayIndexOutOfBoundsException if {@code index < 0}.
732      */
733     @Override
734     public void setElement(int index, double value) {
735         if (index < 0) {
736             throw new ArrayIndexOutOfBoundsException(index);
737         }
738         if (index + 1 > numElements) {
739             numElements = index + 1;
740         }
741         if ((startIndex + index) >= internalArray.length) {
742             expandTo(startIndex + (index + 1));
743         }
744         internalArray[startIndex + index] = value;
745     }
746 
747     /**
748      * This function allows you to control the number of elements contained
749      * in this array, and can be used to "throw out" the last n values in an
750      * array. This function will also expand the internal array as needed.
751      *
752      * @param i a new number of elements
753      * @throws MathIllegalArgumentException if {@code i} is negative.
754      */
755     public void setNumElements(int i) throws MathIllegalArgumentException {
756         // If index is negative thrown an error.
757         if (i < 0) {
758             throw new MathIllegalArgumentException(LocalizedFormats.INDEX_NOT_POSITIVE, i);
759         }
760 
761         // Test the new num elements, check to see if the array needs to be
762         // expanded to accommodate this new number of elements.
763         final int newSize = startIndex + i;
764         if (newSize > internalArray.length) {
765             expandTo(newSize);
766         }
767 
768         // Set the new number of elements to new value.
769         numElements = i;
770     }
771 
772     /**
773      * Returns true if the internal storage array has too many unused
774      * storage positions.
775      *
776      * @return true if array satisfies the contraction criteria
777      */
778     private boolean shouldContract() {
779         if (expansionMode == ExpansionMode.MULTIPLICATIVE) {
780             return (internalArray.length / ((float) numElements)) > contractionCriterion;
781         } else {
782             return (internalArray.length - numElements) > contractionCriterion;
783         }
784     }
785 
786     /**
787      * Returns a copy of the ResizableDoubleArray.  Does not contract before
788      * the copy, so the returned object is an exact copy of this.
789      *
790      * @return a new ResizableDoubleArray with the same data and configuration
791      * properties as this
792      * @since 2.0
793      */
794     public ResizableDoubleArray copy() {
795         return new ResizableDoubleArray(this);
796     }
797 
798     /**
799      * Returns true iff object is a ResizableDoubleArray with the same properties
800      * as this and an identical internal storage array.
801      *
802      * @param object object to be compared for equality with this
803      * @return true iff object is a ResizableDoubleArray with the same data and
804      * properties as this
805      * @since 2.0
806      */
807     @Override
808     public boolean equals(Object object) {
809         if (object == this ) {
810             return true;
811         }
812         if (!(object instanceof ResizableDoubleArray)) {
813             return false;
814         }
815         boolean result = true;
816         final ResizableDoubleArray other = (ResizableDoubleArray) object;
817         result = result && other.contractionCriterion == contractionCriterion;
818         result = result && other.expansionFactor == expansionFactor;
819         result = result && other.expansionMode == expansionMode;
820         result = result && other.numElements == numElements;
821         result = result && other.startIndex == startIndex;
822         if (!result) {
823             return false;
824         } else {
825             return Arrays.equals(internalArray, other.internalArray);
826         }
827     }
828 
829     /**
830      * Returns a hash code consistent with equals.
831      *
832      * @return the hash code representing this {@code ResizableDoubleArray}.
833      * @since 2.0
834      */
835     @Override
836     public int hashCode() {
837         final int[] hashData = new int[6];
838         hashData[0] = Double.valueOf(expansionFactor).hashCode();
839         hashData[1] = Double.valueOf(contractionCriterion).hashCode();
840         hashData[2] = expansionMode.hashCode();
841         hashData[3] = Arrays.hashCode(internalArray);
842         hashData[4] = numElements;
843         hashData[5] = startIndex;
844         return Arrays.hashCode(hashData);
845     }
846 }