You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2022/09/12 12:34:46 UTC
[commons-numbers] branch complex-gsoc-2022 updated: Numbers-186: Added complex non-interleaved list implementation
This is an automated email from the ASF dual-hosted git repository.
aherbert pushed a commit to branch complex-gsoc-2022
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git
The following commit(s) were added to refs/heads/complex-gsoc-2022 by this push:
new 29bb423f Numbers-186: Added complex non-interleaved list implementation
29bb423f is described below
commit 29bb423fe21497e1c75e09b8d4b5127748dad1bd
Author: Sumanth Rajkumar <ra...@gmail.com>
AuthorDate: Mon Sep 12 12:44:16 2022 +0100
Numbers-186: Added complex non-interleaved list implementation
---
.../numbers/complex/arrays/ComplexList.java | 383 ++++++++++++++++-
.../numbers/complex/arrays/ComplexListTest.java | 453 ++++++++++++++-------
2 files changed, 680 insertions(+), 156 deletions(-)
diff --git a/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java b/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java
index 79f83abe..5ebff614 100644
--- a/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java
+++ b/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java
@@ -37,6 +37,17 @@ import java.util.Objects;
*/
public abstract class ComplexList extends AbstractList<Complex> {
+ /**
+ * The maximum size of array to allocate.
+ * Ensuring max capacity is even with additional space for VM array headers.
+ */
+ protected static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 9;
+ /** Default initial capacity. */
+ protected static final int DEFAULT_CAPACITY = 8;
+ /** Memory error message. */
+ protected static final String OOM_ERROR_STRING = "Cannot allocate capacity %s greater than max ";
+ /** Illegal capacity error message. */
+ protected static final String ILLEGAL_CAPCITY = "Illegal capacity: ";
/** Size label message. */
private static final String SIZE_MSG = ", Size: ";
/** Index position label message. */
@@ -231,17 +242,31 @@ public abstract class ComplexList extends AbstractList<Complex> {
/**
* Constructs an empty interleaved list which can store up to the specified capacity without a memory reallocation.
+ * Data is stored in a single {@code double[]} using an interleaved format of {@code (real, imaginary)} pairs.
*
* @param capacity Capacity of interleaved list.
* @return ComplexList object.
- * @throws IllegalArgumentException if the {@code capacity} is greater than {@code MAX_CAPACITY}.
+ * @throws IllegalArgumentException if the {@code capacity} is greater than the maximum capacity or is negative.
*/
public static ComplexList interleaved(int capacity) {
return new ComplexInterleavedList(capacity);
}
+ /**
+ * Constructs an empty non-interleaved list which can store up to the specified capacity without a memory reallocation.
+ * Data is stored using two {@code double[]} arrays, one for each of the {@code real} and {@code imaginary} parts.
+ *
+ * @param capacity Capacity of non-interleaved list.
+ * @return ComplexList object.
+ * @throws IllegalArgumentException if the {@code capacity} is greater than the maximum capacity or is negative.
+ */
+ public static ComplexList nonInterleaved(int capacity) {
+ return new ComplexNonInterleavedList(capacity);
+ }
+
/**
* Constructs an empty interleaved list.
+ * Data is stored in a single {@code double[]} using an interleaved format of {@code (real, imaginary)} pairs.
*
* @return ComplexList object.
*/
@@ -249,20 +274,55 @@ public abstract class ComplexList extends AbstractList<Complex> {
return new ComplexInterleavedList();
}
+ /**
+ * Constructs an empty non-interleaved list.
+ * Data is stored using two {@code double[]} arrays, one for each of the {@code real} and {@code imaginary} parts.
+ *
+ * @return ComplexList object.
+ */
+ public static ComplexList nonInterleaved() {
+ return new ComplexNonInterleavedList();
+ }
+
/**
* Constructs an interleaved list using the specified double array.
- * The data isn't defensively copied, the specified array is used in-place
+ * Data is stored in a single {@code double[]} using an interleaved format of {@code (real, imaginary)} pairs.
+ * <pre>
+ * // N complex numbers
+ * double[] data = {re1, im1, re2, im2, ..., reN, imN};
+ * </pre>
+ *
+ * <p>The data isn't defensively copied, the specified array is used in-place
* and therefore any external modifications to the array will reflect on the list
* unless a structural modification like resize is made to the data storage.
*
* @param data Specified backing double array.
* @return ComplexList object.
* @throws IllegalArgumentException if the specified double array doesn't have an even amount of doubles.
+ * @throws NullPointerException if the specified double array is null.
*/
public static ComplexList from(double[] data) {
return new ComplexInterleavedList(data);
}
+ /**
+ * Constructs a non-interleaved list using the specified double arrays.
+ * Data is stored using two {@code double[]} arrays, one for each of the {@code real} and {@code imaginary} parts.
+ *
+ * <p>The data isn't defensively copied, the specified arrays is used in-place
+ * and therefore any external modifications to the arrays will reflect on the list
+ * unless a structural modification like resize is made to the data storage.
+ *
+ * @param realData Specified backing double array for real parts.
+ * @param imaginaryData Specified backing double array for imaginary parts.
+ * @return ComplexList object.
+ * @throws IllegalArgumentException if the specified double arrays don't have the same amount of doubles.
+ * @throws NullPointerException if either of the specified double arrays are null.
+ */
+ public static ComplexList from(double[] realData, double[] imaginaryData) {
+ return new ComplexNonInterleavedList(realData, imaginaryData);
+ }
+
/**
* Resizable-double array implementation of the List interface. Implements all optional list operations,
* and permits all complex numbers. In addition to implementing the List interface,
@@ -276,23 +336,16 @@ public abstract class ComplexList extends AbstractList<Complex> {
* using instances of Complex.</p>
*
* <p>An application can increase the capacity of an ComplexInterleavedList instance before adding a large number of complex numbers
- * using the ensureCapacity operation. This may reduce the amount of incremental reallocation.</p>
+ * using the ensureCapacityInternal operation. This may reduce the amount of incremental reallocation.</p>
*
* <p>This list does not support {@code null} Complex objects.
*/
private static class ComplexInterleavedList extends ComplexList {
- /**
- * The maximum size of array to allocate.
- * Ensuring max capacity is even with additional space for VM array headers.
- */
- private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 9;
- /** Default initial capacity. */
- private static final int DEFAULT_CAPACITY = 8;
+
/** Max capacity for size of complex numbers in the list. */
private static final int MAX_CAPACITY = MAX_ARRAY_SIZE / 2;
/** Error in case of allocation above max capacity. */
- private static final String OOM_ERROR_STRING = "cannot allocate capacity %s greater than max " + MAX_CAPACITY;
-
+ private static final String OOM_ERROR = OOM_ERROR_STRING + MAX_CAPACITY;
/**
* Storage for the complex numbers.
* Data is stored in an interleaved format using (real, imaginary) pairs.
@@ -303,11 +356,13 @@ public abstract class ComplexList extends AbstractList<Complex> {
* Constructs an empty list which can store up to the specified capacity without a memory reallocation.
*
* @param capacity Capacity of list.
- * @throws IllegalArgumentException if the {@code capacity} is greater than {@code MAX_CAPACITY}.
+ * @throws IllegalArgumentException if the {@code capacity} is greater than the maximum capacity or is negative.
*/
ComplexInterleavedList(int capacity) {
if (capacity > MAX_CAPACITY) {
- throw new IllegalArgumentException(String.format(OOM_ERROR_STRING, capacity));
+ throw new IllegalArgumentException(String.format(OOM_ERROR, capacity));
+ } else if (capacity < 0) {
+ throw new IllegalArgumentException(ILLEGAL_CAPCITY + capacity);
}
final int arrayLength = Math.max(DEFAULT_CAPACITY, capacity) * 2;
realAndImagParts = new double[arrayLength];
@@ -326,6 +381,7 @@ public abstract class ComplexList extends AbstractList<Complex> {
*
* @param fromArray Specified backing double array.
* @throws IllegalArgumentException if the specified double array doesn't have an even amount of doubles.
+ * @throws NullPointerException if the specified double array is null.
*/
ComplexInterleavedList(double[] fromArray) {
if ((fromArray.length & 1) != 0) {
@@ -416,7 +472,7 @@ public abstract class ComplexList extends AbstractList<Complex> {
modCount++;
final long minArrayCapacity = Integer.toUnsignedLong(minCapacity) << 1;
if (minArrayCapacity > MAX_ARRAY_SIZE) {
- throw new OutOfMemoryError(String.format(OOM_ERROR_STRING, minArrayCapacity));
+ throw new OutOfMemoryError(String.format(OOM_ERROR, minArrayCapacity));
}
final long oldArrayCapacity = realAndImagParts.length;
if (minArrayCapacity > oldArrayCapacity) {
@@ -566,4 +622,301 @@ public abstract class ComplexList extends AbstractList<Complex> {
}
}
}
+
+ /**
+ * Resizable-double arrays implementation of the List interface. Implements all optional list operations,
+ * and permits all complex numbers. In addition to implementing the List interface,
+ * this class provides methods to manipulate the size of the arrays that are used internally to store the list.
+ *
+ * <p>Each ComplexNonInterleavedList instance has a capacity. The capacity is the size of the double arrays used to store the complex numbers
+ * in the list. As complex numbers are added to an ComplexNonInterleavedList, its capacity grows automatically.
+ * The complex number is stored using an non-interleaved format and so the maximum number of complex numbers that may be added is
+ * approximately 2<sup>31</sup>. This is also the maximum capacity of java.util.ArrayList.
+ * The memory usage is more efficient than using a List of Complex objects as the underlying numbers are not stored
+ * using instances of Complex.</p>
+ *
+ * <p>An application can increase the capacity of an ComplexNonInterleavedList instance before adding a large number of complex numbers
+ * using the ensureCapacityInternal operation. This may reduce the amount of incremental reallocation.</p>
+ *
+ * <p>This list does not support {@code null} Complex objects.
+ */
+ private static class ComplexNonInterleavedList extends ComplexList {
+
+ /** Max capacity for size of complex numbers in the list. */
+ private static final int MAX_CAPACITY = MAX_ARRAY_SIZE;
+ /** Error in case of allocation above max capacity. */
+ private static final String OOM_ERROR = OOM_ERROR_STRING + MAX_CAPACITY;
+
+ /**
+ * Storage for the real parts of complex numbers.
+ */
+ private double[] realParts;
+ /**
+ * Storage for the imaginary parts of complex numbers.
+ */
+ private double[] imaginaryParts;
+
+ /**
+ * Constructs an empty list which can store up to the specified capacity without a memory reallocation.
+ *
+ * @param capacity Capacity of list.
+ * @throws IllegalArgumentException if the {@code capacity} is greater than the maximum capacity or is negative.
+ */
+ ComplexNonInterleavedList(int capacity) {
+ if (capacity > MAX_CAPACITY) {
+ throw new IllegalArgumentException(String.format(OOM_ERROR, capacity));
+ } else if (capacity < 0) {
+ throw new IllegalArgumentException(ILLEGAL_CAPCITY + capacity);
+ }
+ final int arrayLength = Math.max(DEFAULT_CAPACITY, capacity);
+ realParts = new double[arrayLength];
+ imaginaryParts = new double[arrayLength];
+ }
+
+ /**
+ * Constructs an empty list.
+ */
+ ComplexNonInterleavedList() {
+ realParts = new double[DEFAULT_CAPACITY];
+ imaginaryParts = new double[DEFAULT_CAPACITY];
+ }
+
+ /**
+ * Constructs a non-interleaved list using the specified double arrays.
+ * The data isn't defensively copied, the specified arrays is used in-place.
+ *
+ * @param fromReal Specified backing double array for real parts.
+ * @param fromImaginary Specified backing double array for imaginary parts.
+ * @throws IllegalArgumentException if the specified double arrays don't have the same amount of doubles.
+ * @throws NullPointerException if either of the specified double arrays are null.
+ */
+ ComplexNonInterleavedList(double[] fromReal, double[] fromImaginary) {
+ if (fromReal.length != fromImaginary.length) {
+ throw new IllegalArgumentException("Need the same amount of real and imaginary parts");
+ }
+ realParts = fromReal;
+ imaginaryParts = fromImaginary;
+ size = fromReal.length;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Complex get(int index) {
+ rangeCheck(index);
+ return Complex.ofCartesian(realParts[index], imaginaryParts[index]);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double getReal(int index) {
+ rangeCheck(index);
+ return realParts[index];
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double getImaginary(int index) {
+ rangeCheck(index);
+ return imaginaryParts[index];
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Complex set(int index, Complex number) {
+ rangeCheck(index);
+ final Complex oldValue = Complex.ofCartesian(realParts[index], imaginaryParts[index]);
+ realParts[index] = number.getReal();
+ imaginaryParts[index] = number.getImaginary();
+ return oldValue;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setReal(int index, double real) {
+ rangeCheck(index);
+ realParts[index] = real;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setImaginary(int index, double imaginary) {
+ rangeCheck(index);
+ imaginaryParts[index] = imaginary;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ double[] toArrayReal() {
+ return Arrays.copyOf(realParts, size);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ double[] toArrayImaginary() {
+ return Arrays.copyOf(imaginaryParts, size);
+ }
+
+ /**
+ * Increases the capacity of this ComplexNonInterleavedList instance, if necessary, to ensure that it can hold at
+ * least the amount of complex numbers specified by the minimum capacity argument.
+ *
+ * @param minCapacity Desired minimum capacity.
+ * @throws OutOfMemoryError if the {@code minCapacity} is greater than {@code MAX_ARRAY_SIZE}.
+ */
+ private void ensureCapacityInternal(int minCapacity) {
+ modCount++;
+ final long minArrayCapacity = Integer.toUnsignedLong(minCapacity);
+ if (minArrayCapacity > MAX_ARRAY_SIZE) {
+ throw new OutOfMemoryError(String.format(OOM_ERROR, minArrayCapacity));
+ }
+ final long oldArrayCapacity = realParts.length;
+ if (minArrayCapacity > oldArrayCapacity) {
+ long newArrayCapacity = oldArrayCapacity + (oldArrayCapacity >> 1);
+
+ // Ensure minArrayCapacity <= newArrayCapacity <= MAX_ARRAY_SIZE
+ // Note: At this point minArrayCapacity <= MAX_ARRAY_SIZE
+ if (newArrayCapacity > MAX_ARRAY_SIZE) {
+ newArrayCapacity = MAX_ARRAY_SIZE;
+ } else if (newArrayCapacity < minArrayCapacity) {
+ newArrayCapacity = minArrayCapacity;
+ }
+ realParts = Arrays.copyOf(realParts, (int) newArrayCapacity);
+ imaginaryParts = Arrays.copyOf(imaginaryParts, (int) newArrayCapacity);
+ }
+ }
+
+ /**
+ * Increases the capacity of this ComplexNonInterleavedList instance, if necessary, to ensure that it can hold at
+ * least an additional amount of complex numbers specified by the capacity argument.
+ *
+ * @param capacity Desired capacity.
+ */
+ private void expand(int capacity) {
+ ensureCapacityInternal(size + capacity);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean add(Complex number) {
+ if (size == realParts.length) {
+ ensureCapacityInternal(size + 1);
+ }
+ final int i = size;
+ realParts[i] = number.real();
+ imaginaryParts[i] = number.imag();
+ size++;
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void add(int index, Complex number) {
+ rangeCheckForInsert(index);
+ if (size == realParts.length) {
+ ensureCapacityInternal(size + 1);
+ }
+ final double real = number.real();
+ final double imaginary = number.imag();
+ final int s = size;
+ System.arraycopy(realParts, index, realParts, index + 1, s - index);
+ System.arraycopy(imaginaryParts, index, imaginaryParts, index + 1, s - index);
+ realParts[index] = real;
+ imaginaryParts[index] = imaginary;
+ size++;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean addAll(Collection<? extends Complex> c) {
+ final int numNew = c.size();
+ expand(numNew);
+ double[] realData = new double[numNew];
+ double[] imaginaryData = new double[numNew];
+ int i = 0;
+ for (final Complex val : c) {
+ realData[i] = val.getReal();
+ imaginaryData[i] = val.getImaginary();
+ i++;
+ }
+ final int s = size;
+ System.arraycopy(realData, 0, realParts, s, realData.length);
+ System.arraycopy(imaginaryData, 0, imaginaryParts, s, imaginaryData.length);
+ size += numNew;
+ return numNew != 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean addAll(int index, Collection<? extends Complex> c) {
+ rangeCheckForInsert(index);
+ final int numNew = c.size();
+ expand(numNew);
+ double[] realData = new double[numNew];
+ double[] imaginaryData = new double[numNew];
+ int i = 0;
+ for (final Complex val : c) {
+ realData[i] = val.getReal();
+ imaginaryData[i] = val.getImaginary();
+ i++;
+ }
+ final int numMoved = size - index;
+ System.arraycopy(realData, index, realParts, index + numNew, numMoved);
+ System.arraycopy(realParts, 0, realParts, index, realData.length);
+ System.arraycopy(imaginaryData, index, imaginaryParts, index + numNew, numMoved);
+ System.arraycopy(imaginaryParts, 0, imaginaryParts, index, imaginaryData.length);
+ size += numNew;
+ return numNew != 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Complex remove(int index) {
+ rangeCheck(index);
+ modCount++;
+ final int s = size;
+ final Complex oldValue = Complex.ofCartesian(realParts[index], imaginaryParts[index]);
+ final int numMoved = s - index - 1;
+ if (numMoved > 0) {
+ System.arraycopy(realParts, index + 1, realParts, index, numMoved);
+ System.arraycopy(imaginaryParts, index + 1, imaginaryParts, index, numMoved);
+ }
+ size--;
+ return oldValue;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void replaceAll(ComplexUnaryOperator<Void> operator) {
+ Objects.requireNonNull(operator);
+ final double[] realData = this.realParts;
+ final double[] imaginaryData = this.imaginaryParts;
+ final int m = size;
+ final int expectedModCount = modCount;
+ for (int i = 0; i < m; i++) {
+ final int index = i;
+ operator.apply(realData[i], imaginaryData[i], (x, y) -> {
+ realData[index] = x;
+ imaginaryData[index] = y;
+ return null;
+ });
+ }
+ // check for comodification
+ if (modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ modCount++;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void forEach(ComplexConsumer action) {
+ Objects.requireNonNull(action);
+ final double[] realData = this.realParts;
+ final double[] imaginaryData = this.imaginaryParts;
+ final int m = size;
+ for (int i = 0; i < m; i++) {
+ action.accept(realData[i], imaginaryData[i]);
+ }
+ }
+ }
}
diff --git a/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java b/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java
index fd38d9b8..993e05ca 100644
--- a/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java
+++ b/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java
@@ -24,21 +24,117 @@ import org.apache.commons.numbers.complex.ComplexUnaryOperator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
+import java.util.function.IntFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import java.util.stream.Stream;
-public class ComplexListTest {
+/**
+ * Tests for {@link ComplexList}.
+ */
+class ComplexListTest {
+
+ private static final int MAX_CAPACITY_INTERLEAVED = (Integer.MAX_VALUE - 9) / 2;
+ private static final int MAX_CAPACITY_NON_INTERLEAVED = Integer.MAX_VALUE - 9;
+
+ /**
+ * Generate a stream of arguments containing empty {@code Complex<List>} implementations.
+ *
+ * @return the stream of arguments
+ */
+ static Stream<Arguments> listImplementations() {
+ return Stream.of(
+ Arguments.of(ComplexList.interleaved()),
+ Arguments.of(ComplexList.nonInterleaved())
+ );
+ }
- private static final int MAX_CAPACITY = (Integer.MAX_VALUE - 9) / 2;
+ /**
+ * Generate a stream of arguments containing {@code Complex<List>} implementations with a set size.
+ *
+ * @return the stream of arguments
+ */
+ static Stream<Arguments> listImplementationsWithSize() {
+ return Stream.of(
+ Arguments.of(ComplexList.interleaved(), 2),
+ Arguments.of(ComplexList.nonInterleaved(), 2)
+ );
+ }
+
+ /**
+ * Helper method for testing list capacity exceptions.
+ * Generates a stream of arguments containing {@code Complex<List>} constructors and their capacities.
+ *
+ * @return the stream of arguments
+ */
+ static Stream<Arguments> listConstructorsWithCapacity() {
+ return Stream.of(
+ Arguments.of((IntFunction<List<Complex>>) ComplexList::interleaved, MAX_CAPACITY_INTERLEAVED),
+ Arguments.of((IntFunction<List<Complex>>) ComplexList::nonInterleaved, MAX_CAPACITY_NON_INTERLEAVED)
+ );
+ }
+
+ /**
+ * Generates a stream of arguments containing populated {@code Complex<List>} implementations of a set size.
+ *
+ * @return the stream of arguments
+ */
+ static Stream<Arguments> generateList() {
+ return Stream.of(
+ Arguments.of(generateComplexInterleavedList(10)),
+ Arguments.of(generateComplexNonInterleavedList(10))
+ );
+ }
+
+ /**
+ * Generates a ComplexList in interleaved format of random complex numbers of the given size.
+ *
+ * @param size number of complex numbers in the list.
+ * @return the ComplexList of random complex numbers.
+ */
+ private static ComplexList generateComplexInterleavedList(int size) {
+ List<Complex> objectList = generateList(size);
+ ComplexList list = ComplexList.interleaved();
+ list.addAll(objectList);
+ Assertions.assertEquals(objectList, list);
+ return list;
+ }
+
+ /**
+ * Generates a ComplexList in non-interleaved format of random complex numbers of the given size.
+ *
+ * @param size number of complex numbers in the list.
+ * @return the ComplexList of random complex numbers.
+ */
+ private static ComplexList generateComplexNonInterleavedList(int size) {
+ List<Complex> objectList = generateList(size);
+ ComplexList list = ComplexList.nonInterleaved();
+ list.addAll(objectList);
+ Assertions.assertEquals(objectList, list);
+ return list;
+ }
+
+ /**
+ * Generates a list of random complex numbers of the given size.
+ *
+ * @param size number of complex numbers in the list.
+ * @return the list of random complex numbers.
+ */
+ private static List<Complex> generateList(int size) {
+ return ThreadLocalRandom.current().doubles(size, -Math.PI, Math.PI)
+ .mapToObj(Complex::ofCis).collect(Collectors.toList());
+ }
@Test
- void testFromArray() {
+ void testFromInterleaved() {
int size = 3;
double[] fromArray1 = ThreadLocalRandom.current().doubles(size * 2, -Math.PI, Math.PI).toArray();
ComplexList list = ComplexList.from(fromArray1);
@@ -47,23 +143,62 @@ public class ComplexListTest {
}
double[] fromArray2 = ThreadLocalRandom.current().doubles(5, -Math.PI, Math.PI).toArray();
Assertions.assertThrows(IllegalArgumentException.class, () -> ComplexList.from(fromArray2));
+
+ //testing NullPointerException
+ Assertions.assertThrows(NullPointerException.class, () -> ComplexList.from(null));
}
@Test
- void testGetAndSetMethod() {
- assertListOperation(list -> {
- list.add(Complex.ofCartesian(42, 13));
- list.addAll(1, list);
- list.addAll(list);
- list.set(2, Complex.ofCartesian(200, 1));
- return list.get(2);
- });
+ void testFromNonInterleaved() {
+ int size = 3;
+ double[] fromRealArray1 = ThreadLocalRandom.current().doubles(size, -Math.PI, Math.PI).toArray();
+ double[] fromImaginaryArray1 = ThreadLocalRandom.current().doubles(size, -Math.PI, Math.PI).toArray();
+ ComplexList list = ComplexList.from(fromRealArray1, fromImaginaryArray1);
+ for (int i = 0; i < size; i++) {
+ Assertions.assertEquals(Complex.ofCartesian(fromRealArray1[i], fromImaginaryArray1[i]), list.get(i));
+ }
+
+ double[] fromRealArray2 = ThreadLocalRandom.current().doubles(5, -Math.PI, Math.PI).toArray();
+ double[] fromImaginaryArray2 = ThreadLocalRandom.current().doubles(4, -Math.PI, Math.PI).toArray();
+ Assertions.assertThrows(IllegalArgumentException.class, () -> ComplexList.from(fromRealArray2, fromImaginaryArray2));
+
+ //testing NullPointerException
+ Assertions.assertThrows(NullPointerException.class, () -> ComplexList.from(null, null));
+ Assertions.assertThrows(NullPointerException.class, () -> ComplexList.from(fromRealArray2, null));
+ Assertions.assertThrows(NullPointerException.class, () -> ComplexList.from(null, fromImaginaryArray2));
+ }
+
+ @Test
+ void testFromEmptyInterleaved() {
+ final List<Complex> list = ComplexList.from(new double[0]);
+ final Complex c = Complex.ofCartesian(1, 2);
+ list.add(c);
+ Assertions.assertEquals(Arrays.asList(c), list);
}
@Test
- void testAddAndAddAll() {
+ void testFromEmptyNonInterleaved() {
+ final List<Complex> list = ComplexList.from(new double[0], new double[0]);
+ final Complex c = Complex.ofCartesian(1, 2);
+ list.add(c);
+ Assertions.assertEquals(Arrays.asList(c), list);
+ }
+
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testGetAndSetMethod(List<Complex> l2) {
+ List<Complex> l1 = new ArrayList<>();
+ assertListOperation(list -> list.add(Complex.ofCartesian(42, 13)), l1, l2);
+ assertListOperation(list -> list.addAll(1, list), l1, l2);
+ assertListOperation(list -> list.addAll(list), l1, l2);
+ assertListOperation(list -> list.set(2, Complex.ofCartesian(200, 1)), l1, l2);
+ assertListOperation(list -> list.get(2), l1, l2);
+ }
+
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testAddAndAddAllList(List<Complex> l2) {
List<Complex> l1 = new ArrayList<>();
- List<Complex> l2 = ComplexList.interleaved();
assertListOperation(list -> list.add(Complex.ofCartesian(1, 2)), l1, l2);
assertListOperation(list -> {
list.add(1, Complex.ofCartesian(10, 20));
@@ -79,80 +214,96 @@ public class ComplexListTest {
assertListOperation(list -> list.add(Complex.ofCartesian(19, 20)), l1, l2);
assertListOperation(list -> list.add(Complex.ofCartesian(21, 22)), l1, l2);
assertListOperation(list -> list.add(Complex.ofCartesian(23, 24)), l1, l2);
+ }
- //Testing add at an index for branch condition (size == realAndImagParts.length >>> 1)
- List<Complex> l3 = new ArrayList<>();
- List<Complex> l4 = ComplexList.interleaved();
- assertListOperation(list -> list.add(Complex.ofCartesian(1, 2)), l3, l4);
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testAddAndAddAtIndexBranchCondition(List<Complex> l2) {
+ //Testing add at an index for branch condition (size == realAndImagParts.length >>> 1) and (size == real.length)
+ List<Complex> l1 = new ArrayList<>();
+ assertListOperation(list -> list.add(Complex.ofCartesian(1, 2)), l1, l2);
assertListOperation(list -> {
list.add(1, Complex.ofCartesian(10, 20));
return Boolean.TRUE;
- }, l3, l4);
- assertListOperation(list -> list.add(Complex.ofCartesian(13, 14)), l3, l4);
- assertListOperation(list -> list.add(Complex.ofCartesian(15, 16)), l3, l4);
- assertListOperation(list -> list.add(Complex.ofCartesian(17, 18)), l3, l4);
+ }, l1, l2);
+ assertListOperation(list -> list.add(Complex.ofCartesian(13, 14)), l1, l2);
+ assertListOperation(list -> list.add(Complex.ofCartesian(15, 16)), l1, l2);
+ assertListOperation(list -> list.add(Complex.ofCartesian(17, 18)), l1, l2);
assertListOperation(list -> {
list.addAll(1, list);
return Boolean.TRUE;
- }, l3, l4);
- assertListOperation(list -> list.add(Complex.ofCartesian(19, 20)), l3, l4);
- assertListOperation(list -> list.add(Complex.ofCartesian(21, 22)), l3, l4);
+ }, l1, l2);
+ assertListOperation(list -> list.add(Complex.ofCartesian(19, 20)), l1, l2);
+ assertListOperation(list -> list.add(Complex.ofCartesian(21, 22)), l1, l2);
assertListOperation(list -> {
list.add(1, Complex.ofCartesian(10, 20));
return Boolean.TRUE;
- }, l3, l4);
+ }, l1, l2);
+ }
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testAddAndAddAllEnsureCapacityBranchConditions(List<Complex> l2) {
//Testing branch condition (newArrayCapacity < minArrayCapacity) in ensureCapacity
- ComplexList list1 = ComplexList.interleaved();
int size = 5;
+ List<Complex> list1 = new ArrayList<>(size);
IntStream.range(0, size).mapToObj(i -> Complex.ofCartesian(i, -i)).forEach(list1::add);
- List<Complex> l5 = new ArrayList<>();
- List<Complex> l6 = ComplexList.interleaved();
- assertListOperation(list -> list.add(Complex.ofCartesian(1, 2)), l5, l6);
+ List<Complex> l1 = new ArrayList<>();
+ assertListOperation(list -> list.add(Complex.ofCartesian(1, 2)), l1, l2);
// Expand the list by doubling in size until at the known minArrayCapacity
- while (l5.size() < 8) {
- assertListOperation(list -> list.addAll(list), l5, l6);
+ while (l1.size() < 8) {
+ assertListOperation(list -> list.addAll(list), l1, l2);
}
- assertListOperation(list -> list.addAll(list1), l5, l6);
+ assertListOperation(list -> list.addAll(list1), l1, l2);
+ }
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testAddingEmptyListToEmptyList(List<Complex> l2) {
//Test for adding an empty list to an empty list
- ComplexList list = ComplexList.interleaved();
- assertListOperation(l -> {
- l.addAll(list);
- return l.addAll(0, list);
- });
+ List<Complex> l1 = new ArrayList<>();
+ List<Complex> list2 = new ArrayList<>();
+ assertListOperation(list -> list.addAll(list2), l1, l2);
+ assertListOperation(list -> list.addAll(0, list2), l1, l2);
}
- @Test
- void testSetAddAndAddAllNullPointerException() {
- ComplexList copy = ComplexList.interleaved();
- ComplexList list1 = generateComplexList(3);
- copy.addAll(list1);
- Assertions.assertThrows(NullPointerException.class, () -> list1.add(null));
- Assertions.assertThrows(NullPointerException.class, () -> list1.add(0, null));
- Assertions.assertThrows(NullPointerException.class, () -> list1.set(1, null));
+ @ParameterizedTest
+ @MethodSource({"listImplementationsWithSize"})
+ void testSetAddAndAddAllNullPointerException(List<Complex> list, int size) {
+ List<Complex> expected = new ArrayList<>();
+ IntStream.range(0, size).forEach(i -> expected.add(Complex.ofCartesian(i, -i)));
+
+ list.addAll(expected);
+
+ Assertions.assertEquals(expected, list);
+
+ Assertions.assertThrows(NullPointerException.class, () -> list.add(null));
+ Assertions.assertThrows(NullPointerException.class, () -> list.add(0, null));
+ Assertions.assertThrows(NullPointerException.class, () -> list.set(1, null));
List<Complex> list2 = generateList(3);
list2.set(1, null);
- Assertions.assertThrows(NullPointerException.class, () -> list1.addAll(list2));
- Assertions.assertThrows(NullPointerException.class, () -> list1.addAll(0, list2));
- Assertions.assertEquals(copy, list1);
+ Assertions.assertThrows(NullPointerException.class, () -> list.addAll(list2));
+ Assertions.assertThrows(NullPointerException.class, () -> list.addAll(0, list2));
+
+ // Check no modifications were made
+ Assertions.assertEquals(expected, list);
}
- @Test
- void testRemove() {
- assertListOperation(list -> {
- list.add(Complex.ofCartesian(42, 13));
- list.addAll(list);
- list.remove(0);
- return list.remove(0);
- });
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testRemove(List<Complex> l2) {
+ List<Complex> l1 = new ArrayList<>();
+ assertListOperation(list -> list.add(Complex.ofCartesian(42, 13)), l1, l2);
+ assertListOperation(list -> list.addAll(list), l1, l2);
+ assertListOperation(list -> list.remove(0), l1, l2);
+ assertListOperation(list -> list.remove(0), l1, l2);
}
- @Test
- void testGetAndSetIndexOutOfBoundExceptions() {
- ComplexList list = ComplexList.interleaved();
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testGetAndSetIndexOutOfBoundExceptions(List<Complex> list) {
// Empty list throws
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.get(0));
int size = 5;
@@ -167,9 +318,9 @@ public class ComplexListTest {
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.set(size + 1, Complex.ofCartesian(200, 1)));
}
- @Test
- void testAddIndexOutOfBoundExceptions() {
- ComplexList list = ComplexList.interleaved();
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testAddIndexOutOfBoundExceptions(List<Complex> list) {
int size = 5;
IntStream.range(0, size).mapToObj(i -> Complex.ofCartesian(i, -i)).forEach(list::add);
@@ -179,45 +330,76 @@ public class ComplexListTest {
list.add(size + 1, Complex.ofCartesian(42, 13)));
}
- @Test
- void testRemoveIndexOutOfBoundExceptions() {
- ComplexList list = generateComplexList(2);
+ @ParameterizedTest
+ @MethodSource({"listImplementationsWithSize"})
+ void testRemoveIndexOutOfBoundExceptions(List<Complex> list, int size) {
+ IntStream.range(0, size).forEach(i -> list.add(Complex.ofCartesian(i, -i)));
list.remove(0);
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.remove(1));
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1));
}
+ /**
+ * Helper method for testInitialSize.
+ * Generates a stream of arguments containing {@code Complex<List>} constructors with different initial sizes.
+ *
+ * @return the stream of arguments
+ */
+ static Stream<Arguments> testInitialSize() {
+ return Stream.of(
+ Arguments.of((IntFunction<List<Complex>>) ComplexList::interleaved, 0),
+ Arguments.of((IntFunction<List<Complex>>) ComplexList::interleaved, 1),
+ Arguments.of((IntFunction<List<Complex>>) ComplexList::interleaved, 10),
+ Arguments.of((IntFunction<List<Complex>>) ComplexList::nonInterleaved, 0),
+ Arguments.of((IntFunction<List<Complex>>) ComplexList::nonInterleaved, 1),
+ Arguments.of((IntFunction<List<Complex>>) ComplexList::nonInterleaved, 10)
+ );
+ }
+
@ParameterizedTest
- @ValueSource(ints = {0, 1, 10})
- void testConstructor(int size) {
+ @MethodSource
+ void testInitialSize(IntFunction<List<Complex>> constructor, int size) {
List<Complex> l1 = new ArrayList<>(size);
- List<Complex> l2 = ComplexList.interleaved(size);
+ List<Complex> l2 = constructor.apply(size);
+
Assertions.assertEquals(l1, l2);
- assertListOperation(l -> l.add(Complex.ofCartesian(10, 20)), l1, l2);
- assertListOperation(l -> {
- l.add(1, Complex.ofCartesian(10, 20));
+
+ assertListOperation(list -> list.add(Complex.ofCartesian(10, 20)), l1, l2);
+ assertListOperation(list -> {
+ list.add(1, Complex.ofCartesian(10, 20));
return Boolean.TRUE;
}, l1, l2);
- assertListOperation(l -> l.addAll(1, l), l1, l2);
+ assertListOperation(list -> list.addAll(1, list), l1, l2);
}
- @Test
- void testCapacityExceptions() {
- Assertions.assertThrows(IllegalArgumentException.class, () -> ComplexList.interleaved(MAX_CAPACITY + 1));
+ @ParameterizedTest
+ @MethodSource({"listConstructorsWithCapacity"})
+ void testCapacityIllegalArgumentException(IntFunction<List<Complex>> constructor, int maxCapacity) {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> constructor.apply(-1));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> constructor.apply(maxCapacity + 1));
+ }
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testCapacityOutOfMemoryException(List<Complex> list) {
// Set-up required sizes
- ComplexList list = ComplexList.interleaved();
- List<Complex> l = new SizedList(Integer.MAX_VALUE);
- Assertions.assertThrows(OutOfMemoryError.class, () -> list.addAll(l));
+ List<Complex> tooLarge = new SizedList(Integer.MAX_VALUE);
+ Assertions.assertThrows(OutOfMemoryError.class, () -> list.addAll(tooLarge));
+ }
- List<Complex> l2 = new SizedList(MAX_CAPACITY + 1);
- Assertions.assertThrows(OutOfMemoryError.class, () -> list.addAll(l2));
+ @ParameterizedTest
+ @MethodSource({"listConstructorsWithCapacity"})
+ void testCapacityOutOfMemoryExceptions(IntFunction<List<Complex>> constructor, int maxCapacity) {
+ // Set-up required sizes
+ List<Complex> list = constructor.apply(0);
+ List<Complex> tooLarge = new SizedList(maxCapacity + 1);
+ Assertions.assertThrows(OutOfMemoryError.class, () -> list.addAll(tooLarge));
}
- @Test
- void testReplaceAllComplexUnaryOperator() {
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testReplaceAllComplexUnaryOperator(ComplexList actualList) {
List<Complex> objectList = generateList(10);
- ComplexList actualList = ComplexList.interleaved();
actualList.addAll(objectList);
Assertions.assertThrows(NullPointerException.class, () -> actualList.replaceAll((ComplexUnaryOperator<Void>) null));
objectList.replaceAll(Complex::conj);
@@ -225,64 +407,80 @@ public class ComplexListTest {
Assertions.assertEquals(objectList, actualList);
}
- @Test
- void testReplaceAllComplexBinaryOperator() {
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testReplaceAllComplexBinaryOperator(ComplexList actualList) {
List<Complex> objectList = generateList(10);
double r = 2;
double i = 3;
Complex multiplier = Complex.ofCartesian(r, i);
- ComplexList actualList = ComplexList.interleaved();
actualList.addAll(objectList);
objectList.replaceAll(c -> c.multiply(multiplier));
actualList.replaceAll((x, y, action) -> ComplexFunctions.multiply(x, y, r, i, action));
Assertions.assertEquals(objectList, actualList);
+
}
- @Test
- void testReplaceAllComplexScalarFunction() {
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testReplaceAllComplexScalarFunction(ComplexList actualList) {
List<Complex> objectList = generateList(10);
double factor = 2;
- ComplexList actualList = ComplexList.interleaved();
actualList.addAll(objectList);
objectList.replaceAll(c -> c.pow(factor));
actualList.replaceAll((x, y, action) -> ComplexFunctions.pow(x, y, factor, action));
Assertions.assertEquals(objectList, actualList);
}
+ /**
+ * Helper method for testForEachComplexConsumer.
+ * Generates a stream of arguments containing populated {@code Complex<List>} implementations of different set sizes.
+ *
+ * @return the stream of arguments
+ */
+ static Stream<Arguments> testForEachComplexConsumer() {
+ return Stream.of(
+ Arguments.of(generateComplexInterleavedList(0)),
+ Arguments.of(generateComplexInterleavedList(10)),
+ Arguments.of(generateComplexNonInterleavedList(0)),
+ Arguments.of(generateComplexNonInterleavedList(10))
+ );
+ }
+
@ParameterizedTest
- @ValueSource(ints = {0, 10})
- void testForEachComplexConsumer(int size) {
- ComplexList expected = generateComplexList(size);
+ @MethodSource
+ void testForEachComplexConsumer(ComplexList expected) {
ArrayList<Complex> actual = new ArrayList<>();
Assertions.assertThrows(NullPointerException.class, () -> expected.forEach((ComplexConsumer) null));
expected.forEach((real, imaginary) -> actual.add(Complex.ofCartesian(real, imaginary)));
Assertions.assertEquals(expected, actual);
}
- @Test
- void testGetRealAndImaginary() {
- ComplexList list = generateComplexList(10);
- for (int i = 0; i < list.size(); i++) {
- Assertions.assertEquals(list.get(i).getReal(), list.getReal(i), "real");
- Assertions.assertEquals(list.get(i).getImaginary(), list.getImaginary(i), "imaginary");
+ @ParameterizedTest
+ @MethodSource({"generateList"})
+ void testGetRealAndImaginary(ComplexList expected) {
+ for (int i = 0; i < expected.size(); i++) {
+ Assertions.assertEquals(expected.get(i).getReal(), expected.getReal(i), "real");
+ Assertions.assertEquals(expected.get(i).getImaginary(), expected.getImaginary(i), "imaginary");
}
}
- @Test
- void testSetRealAndImaginary() {
- ComplexList list = generateComplexList(10);
- for (int i = 0; i < list.size(); i++) {
- final double value = Math.PI * i;
- list.setReal(i, value);
- list.setImaginary(i, value);
- Assertions.assertEquals(value, list.get(i).getReal());
- Assertions.assertEquals(value, list.get(i).getImaginary());
+ @ParameterizedTest
+ @MethodSource({"generateList"})
+ void testSetRealAndImaginary(ComplexList expected) {
+ for (int i = 0; i < expected.size(); i++) {
+ final double real = Math.PI * (i + 1);
+ final double imag = Math.E * (i + 1);
+ expected.setReal(i, real);
+ expected.setImaginary(i, imag);
+ Assertions.assertEquals(real, expected.get(i).getReal());
+ Assertions.assertEquals(imag, expected.get(i).getImaginary());
}
}
- @Test
- void testGetAndSetRealAndImaginaryIndexOutOfBoundsException() {
- ComplexList list = ComplexList.interleaved();
+ @ParameterizedTest
+ @MethodSource({"listImplementations"})
+ void testGetAndSetRealAndImaginaryIndexOutOfBoundsException(ComplexList list) {
// Empty list throws
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.getReal(0));
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.getImaginary(0));
@@ -307,9 +505,9 @@ public class ComplexListTest {
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.setImaginary(size + 1, 200));
}
- @Test
- void testToArrayRealAndImaginary() {
- ComplexList list = generateComplexList(10);
+ @ParameterizedTest
+ @MethodSource({"generateList"})
+ void testToArrayRealAndImaginary(ComplexList list) {
double[] expectedReal = list.stream().mapToDouble(Complex::getReal).toArray();
double[] actualReal = list.toArrayReal();
Assertions.assertArrayEquals(expectedReal, actualReal);
@@ -318,29 +516,6 @@ public class ComplexListTest {
Assertions.assertArrayEquals(expectedImaginary, actualImaginary);
}
- /**
- * Generates a ComplexList of random complex numbers of the given size.
- * @param size number of complex numbers in the list.
- * @return the ComplexList of random complex numbers.
- */
- private static ComplexList generateComplexList(int size) {
- List<Complex> objectList = generateList(size);
- ComplexList list = ComplexList.interleaved();
- list.addAll(objectList);
- Assertions.assertEquals(objectList, list);
- return list;
- }
-
- /**
- * Generates a list of random complex numbers of the given size.
- * @param size number of complex numbers in the list.
- * @return the list of random complex numbers.
- */
- private static List<Complex> generateList(int size) {
- return ThreadLocalRandom.current().doubles(size, -Math.PI, Math.PI)
- .mapToObj(Complex::ofCis).collect(Collectors.toList());
- }
-
private static <T> void assertListOperation(Function<List<Complex>, T> operation,
List<Complex> l1, List<Complex> l2) {
T t1 = operation.apply(l1);
@@ -349,10 +524,6 @@ public class ComplexListTest {
Assertions.assertEquals(l1, l2);
}
- private static <T> void assertListOperation(Function<List<Complex>, T> operation) {
- assertListOperation(operation, new ArrayList<>(), ComplexList.interleaved());
- }
-
/**
* This class purposely gives a fixed size and so is a non-functional list.
* It is used to trigger capacity exceptions when adding a collection to ComplexList.