From f039b677b85cde38e3287aa31ee78ad6bc2fd058 Mon Sep 17 00:00:00 2001 From: Tim O'Brien Date: Thu, 15 May 2003 05:19:57 +0000 Subject: [PATCH] Added an expandable double array, this class simply contains a double[] and takes care of automatic expansion of an internal array when necessary. Class added with accompanying unit test git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/math/trunk@140827 13f79535-47bb-0310-9956-ffa450edef68 --- .../commons/math/ExpandableDoubleArray.java | 280 ++++++++++++++++++ .../math/ExpandableDoubleArrayTest.java | 258 ++++++++++++++++ 2 files changed, 538 insertions(+) create mode 100644 src/java/org/apache/commons/math/ExpandableDoubleArray.java create mode 100644 src/test/org/apache/commons/math/ExpandableDoubleArrayTest.java diff --git a/src/java/org/apache/commons/math/ExpandableDoubleArray.java b/src/java/org/apache/commons/math/ExpandableDoubleArray.java new file mode 100644 index 000000000..09eaad221 --- /dev/null +++ b/src/java/org/apache/commons/math/ExpandableDoubleArray.java @@ -0,0 +1,280 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.commons.math; + +import java.util.NoSuchElementException; + +/** + * An array of double primitives which can expand as needed. + * + * @author Tim O'Brien + */ +public class ExpandableDoubleArray { + + // This is the internal storage array. + private double[] internalArray; + + // Number of elements in the array + private int numElements = 0; + + // The initial capacity of the array. + // Initial capacity is not exposed as a property as it is only meaningful + // when passed to a constructor. + private int initialCapacity = 16; + + // The expand factor of the array. When the array need to be expanded, the new array size + // will be internalArray.length * expandFactor + private float expansionFactor = 2.0f; + + /** + * Create an expandable double array with the + * default initial capactiy of 16 and an expansion factor of 2.00 + */ + public ExpandableDoubleArray() { + internalArray = new double[initialCapacity]; + } + + /** + * Create an expandable double array with the + * specified initial capacity and the defult expansion factor of 2.00 + * + * @param initialCapacity The initial size of the internal storage array + */ + public ExpandableDoubleArray(int initialCapacity) { + setInitialCapacity(initialCapacity); + internalArray = new double[this.initialCapacity]; + } + + /** + * Create an expandable double array with the + * specificed initial capacity and expand factor. + * + * @param initialCapacity The initial size of the internal storage array + * @param expansionFactor the array will be expanded based on this parameter + */ + public ExpandableDoubleArray(int initialCapacity, float expansionFactor) { + setInitialCapacity( initialCapacity ); + setExpansionFactor(expansionFactor); + this.initialCapacity = initialCapacity; + internalArray = new double[initialCapacity]; + } + + /** + * The expansion factor controls the size of a new aray when an array needs to be expanded. + * When a value is inserted into a full array, the new array size is calculated as the + * current array size times this expansion factor. The default expansion factor is 2.0 + * + * @return the expansion factor of this expandable double array + */ + public float getExpansionFactor() { + return expansionFactor; + } + + /** + * Sets the expansion factor for this expandable double array. The expansion factor will + * affect the next expansion of this array. + * + * @param expansionFactor the expansion factor of this array + */ + public void setExpansionFactor(float expansionFactor) { + + // The expansion factor *must* be larger than 1.0, otherwise we'll have an inconsistency + // upon expansion we'll start shrinking which will lead to ArrayOutOfBound exceptions. + if (expansionFactor > 1.0) { + this.expansionFactor = expansionFactor; + } else { + throw new IllegalArgumentException( + "The expansion factor must be a number greater than" + "1.0"); + } + } + + /** + * Sets the initial capacity + * + * @param initialCapacity + */ + public void setInitialCapacity(int initialCapacity) { + if (initialCapacity > 0) { + this.initialCapacity = initialCapacity; + } else { + throw new IllegalArgumentException( + "The initial capacity supplied: " + + initialCapacity + + "must be a positive integer"); + } + } + + /** + * Returns the internal storage array + * + * @return the internal storage array used by this object + */ + public double[] getValues() { + return (internalArray); + } + + /** + * Returns the number of elements currently in the array. Please note + * that this is different from the length of the internal storage array. + * @return number of elements + */ + public int getNumElements() { + return (numElements); + } + + /** + * Returns the element at the specified index + * + * @param index index to fetch a value from + * @return value stored at the specified index + */ + public double getElement(int index) throws NoSuchElementException { + double value = Double.NaN; + if (index >= numElements) { + throw new NoSuchElementException( + "The index specified: " + + index + + " is larger than the " + + "current number of elements"); + } else if (index >= 0) { + value = internalArray[index]; + } else { + throw new IllegalArgumentException( + "Elements cannot be retrieved from negative array " + "index"); + } + return value; + } + + /** + * Sets the element at the specified index. This method will expand the internal storage array to + * accomodate the insertion of a value at an index beyond the current capacity. + * @param index index to store a value in + * @param value value to store at the specified index + */ + public synchronized void setElement(int index, double value) { + + if( index < 0 ) { + throw new IllegalArgumentException( "Cannot set an element at a negative index"); + } + + if (index >= internalArray.length) { + expandTo(index + 1); + numElements = index + 1; + } + internalArray[index] = value; + } + + /** + * Expands the internal storage array to the specified size. + * + * @param size Size of the new internal storage array + */ + private synchronized void expandTo(int size) { + double[] tempArray = new double[size]; + // Copy and swap + System.arraycopy(internalArray,0,tempArray,0,internalArray.length); + internalArray = tempArray; + } + + /** + * Expands the internal storage array using the expansion factor + */ + private synchronized void expand() { + + // notice the use of Math.ceil(), this gaurantees 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 = (int) Math.ceil(internalArray.length * expansionFactor); + double[] tempArray = + new double[newSize]; + + // Copy and swap + System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length); + internalArray = tempArray; + } + + /** + * Adds an element to the end of this expandable array + * + * @return value to be added to end of array + */ + public synchronized void addElement(double value) { + numElements++; + if (numElements > internalArray.length) { + expand(); + } + internalArray[numElements - 1] = value; + } + + /** + * Notice the package scope on this method. This method is simply here for the JUnit + * test, it allows us check if the expansion is working properly after a number of expansions. This + * is not meant to be a part of the public interface of this class. + * + * @return the length of the internal storage array. + */ + int getInternalLength() { + return (internalArray.length); + } + + /** + * Clear the array, reset the size to the initialCapacity and the number of elements to zero + */ + public synchronized void clear() { + numElements = 0; + internalArray = new double[initialCapacity]; + } + +} diff --git a/src/test/org/apache/commons/math/ExpandableDoubleArrayTest.java b/src/test/org/apache/commons/math/ExpandableDoubleArrayTest.java new file mode 100644 index 000000000..0c518035b --- /dev/null +++ b/src/test/org/apache/commons/math/ExpandableDoubleArrayTest.java @@ -0,0 +1,258 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.commons.math; + +import java.util.NoSuchElementException; + +import junit.framework.TestCase; + +/** + * This class contains test cases for the ExpandableDoubleArray. + * + * @author Tim O'Brien + */ +public class ExpandableDoubleArrayTest extends TestCase { + + public ExpandableDoubleArrayTest(String name) { + super( name ); + } + + /** TEST NORMAL OPERATIONS **/ + + public void testAdd1000() { + + ExpandableDoubleArray exDoubleArr = new ExpandableDoubleArray(); + + for( int i = 0; i < 1000; i++) { + exDoubleArr.addElement( i ); + } + + assertTrue("Number of elements should be equal to 1000 after adding 1000 values", + exDoubleArr.getNumElements() == 1000); + + assertTrue("Internal Storage length should be 1024 if we started out with initial capacity of " + "16 and an expansion factor of 2.0", + exDoubleArr.getInternalLength() == 1024); + + assertTrue("The element at the 56th index should be 56", + exDoubleArr.getElement(56) == 56 ); + + } + + public void testWithInitialCapacity() { + ExpandableDoubleArray exDoubleArr = new ExpandableDoubleArray(2); + + assertTrue("Initial internal length should be 2", exDoubleArr.getInternalLength() == 2); + assertTrue("Initial number of elements should be 0", exDoubleArr.getNumElements() == 0); + + int iterations = (int) Math.pow(2.0, 15.0); + + for( int i = 0; i < iterations; i++) { + exDoubleArr.addElement( i ); + } + + assertTrue("Number of elements should be equal to 2^15", exDoubleArr.getNumElements() == (int) Math.pow(2.0, 15.0)); + assertTrue("Internal length should be 2^15", exDoubleArr.getInternalLength() == (int) Math.pow(2.0, 15.0)); + + exDoubleArr.addElement( 2.0 ); + + assertTrue("Number of elements should be equals to 2^15 + 1", + exDoubleArr.getNumElements() == ( (int) Math.pow(2.0, 15.0) + 1 ) ); + assertTrue("Internal length should be 2^16", exDoubleArr.getInternalLength() == (int) Math.pow(2.0, 16.0)); + + + } + + public void testWithInitialCapacitAndExpansionFactor() { + ExpandableDoubleArray exDoubleArr = new ExpandableDoubleArray(3, 3.0f); + + assertTrue("Initial internal length should be 3", exDoubleArr.getInternalLength() == 3); + assertTrue("Initial number of elements should be 0", exDoubleArr.getNumElements() == 0); + + int iterations = (int) Math.pow(3.0, 7.0); + + for( int i = 0; i < iterations; i++) { + exDoubleArr.addElement( i ); + } + + assertTrue("Number of elements should be equal to 3^7", exDoubleArr.getNumElements() == (int) Math.pow(3.0, 7.0)); + assertTrue("Internal length should be 3^7", exDoubleArr.getInternalLength() == (int) Math.pow(3.0, 7.0)); + + exDoubleArr.addElement( 2.0 ); + + assertTrue("Number of elements should be equals to 3^7 + 1", + exDoubleArr.getNumElements() == ( (int) Math.pow(3.0, 7.0) + 1 ) ); + assertTrue("Internal length should be 3^8", exDoubleArr.getInternalLength() == (int) Math.pow(3.0, 8.0)); + + assertTrue("Expansion factor should equal 3.0", exDoubleArr.getExpansionFactor() == 3.0f); + } + + public void testGetValues() { + + ExpandableDoubleArray eDA = new ExpandableDoubleArray(); + + double[] controlArray = {2.0, 4.0, 6.0}; + + eDA.addElement(2.0); + eDA.addElement(4.0); + eDA.addElement(6.0); + double[] testArray = eDA.getValues(); + + for( int i = 0; i < eDA.getNumElements(); i++) { + assertTrue( "The testArray values should equal the controlArray values, index i: " + i + + " does not match", testArray[i] == controlArray[i]); + } + + } + + public void testSetElementArbitraryExpansion() { + + ExpandableDoubleArray eDA = new ExpandableDoubleArray(); + + double[] controlArray = {2.0, 4.0, 6.0}; + + eDA.addElement(2.0); + eDA.addElement(4.0); + eDA.addElement(6.0); + eDA.setElement(1, 3.0); + + // Expand the array arbitrarily to 1000 items + eDA.setElement(1000, 3.4); + + assertTrue( "The length of the internal array should now be 1001, it isn't", eDA.getInternalLength() == 1001); + assertTrue( "The number of elements should now be 1001, it isn't", eDA.getNumElements() == 1001); + + assertTrue( "Uninitialized Elements are default value of 0.0, index 766 wasn't", + eDA.getElement( 760 ) == 0.0); + + assertTrue( "The 1000th index should be 3.4, it isn't", eDA.getElement(1000) == 3.4); + assertTrue( "The 0th index should be 2.0, it isn't", eDA.getElement(0) == 2.0); + + } + + /** TEST ERROR CONDITIONS **/ + + public void testIllegalInitialCapacity() { + try { + ExpandableDoubleArray eDA = new ExpandableDoubleArray(-3, 2.0f); + fail( "That constructor should have thrown an IllegalArgumentException because " + "the initialCapacity was negative, if it didn't then" + " the range checking of initialCapacity is not working properly" ); + } catch( IllegalArgumentException iae ) { + } + try { + ExpandableDoubleArray eDA = new ExpandableDoubleArray(0, 2.0f); + fail( "That constructor should have thrown an IllegalArgumentException because " + + "the initialCapacity was ZERO if it didn't then" + + " the range checking of initialCapacity is not working properly" ); + } catch( IllegalArgumentException iae ) { + } + } + + public void testIllegalExpansionFactor() { + try { + ExpandableDoubleArray eDA = new ExpandableDoubleArray(3, 0.66f); + fail( "That constructor should have thrown an IllegalArgumentException because " + "the expansionFactor for 0.66 which would shrink the array instead of expand the array"); + } catch( IllegalArgumentException iae ) { + } + try { + ExpandableDoubleArray eDA = new ExpandableDoubleArray(3, 0.0f); + fail( "That constructor should have thrown an IllegalArgumentException because " + + "the expansionFactor for 0.0"); + } catch( IllegalArgumentException iae) { + } + + try { + ExpandableDoubleArray eDA = new ExpandableDoubleArray(3, -4.35f); + fail( "That constructor should have thrown an IllegalArgumentException because " + + "the expansionFactor for -4.35"); + } catch( IllegalArgumentException iae) { + } + } + + public void testGetOutOfBounds() { + + ExpandableDoubleArray eDA = new ExpandableDoubleArray(); + eDA.addElement(2.0); + eDA.addElement(3.0); + + try { + eDA.getElement(0); + eDA.getElement(1); + } catch( NoSuchElementException nsee ) { + fail( "There are values for index 0 and 1, this should not have thrown an exception"); + } + + try { + eDA.getElement(2); + fail( "There are 2 elements in the array, you asked for index 2 implying that there are 3. " + "exception should have been thrown"); + } catch( NoSuchElementException nsee ) { + } + + try { + eDA.getElement(-234); + fail( "You tried to retrieve a negative index, this should have thrown an exception. " ); + } catch( IllegalArgumentException iae) { + } + } + + public void testSetOutOfBounds() { + + ExpandableDoubleArray eDA = new ExpandableDoubleArray(); + + try { + eDA.setElement( -3, 3.4 ); + fail( "You tried to set an element with a negative index, thisshould have thrown an error"); + } catch( IllegalArgumentException iae ) { + } + } + +}