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.