⌛⌛⌛ N-dimensional array

Place your code into a file named NdArray.java in the directory Round8/ndarray. Remember to pull task material from student_template_project.

In this task you should implement a fixed N-dimensional array as a generic container class NdArray<E>. The idea is that e.g. an NdArray objecd created as new NdArray<String>(2, 5, 4) would represent a 3-dimensional String array whose dimensions have sizes 2, 5 and 4. To be more precise, the first dimension size would be 2, the second 5 and third 4. In similar manner new NdArray<Double>(3, 3, 3, 3, 3) would create a 5-dimensional Double array whose all 5 dimensions have size 3.

The word “fixed” used in the beginning means that the parameters given to the constructor fix the number and sizes of the array dimensions; they cannot be changed later.

The generic class NdArray<E> should have the following public features:

  • Inherits the abstract class AbstractCollection<E> from the Java class library.

    • This requires us to implement the member functions size and iterator described below.

    • Inheriting the class will in effect make NdArray a more or less fully featured Java container container class. NdArray objects could e.g. be processed with streams by using the inherited member function stream().

  • Constructor NdArray(Integer firstDimLen, Integer ...furtherDimLens) that receives one or more dimension sizes.

    • The parameters list the sizes of dimensions 1…``N``.

      • firstDimLen is the size of the first dimension and furtherDimLens lists the sizes of dimensions 2…``N`` (if there is more than one dimension).

        • The first dimension size is received separately in order to force the constructor to receive at least one parameter.

      • Therefore the overall number of dimensions N = number of parameters = 1 + number of furtherDimLens parameters (size of that array).

    • Each dimension size must be non-negative (0 is allowed).

      • If some dimension size is negative, throw a NegativeArraySizeException exception with a message “Illegal dimension size dimLen.”, where dimLen is the first negative dimension size among the parameters.

  • Member function int size() that returns the overall size of the array (multiplication of its dimension sizes). E.g. a 3-dimensional array with dimension sizes 2, 5 and 4 has size 2 · 5 · 4 = 20.

  • Member function E get(int... indices) that returns the item at the array location specified by indices.

    • The number of indices must be equal to the number of dimensions N.

      • If the number of parameters is wrong, throw an IllegalArgumentException exception with a message “The array has N dimensions but x indices were given.”, where N is the number of dimensions and x is the number of indices parameters.

    • Each index must be legal, that is, in the interval 0…size of the corresponding dimension - 1.

      • If any dimension is given an illegal index, throw an``IndexOutOfBoundsException`` exception with a message “Illegal index i for dimension dim of length dimLen."”, where i is the first illegal index within indices, and dim and dimLen are the ordinal number and size of the corresponding dimension. The ordinal numbers of the dimensions are 1, …, N.

      • E.g. if we have a 3-dimensional array with dimension sizes 2, 5 and 4, the call get(i, j, k) is legal if and only if 0 ≤ i < 2, 0 ≤ j < 5 and 0 ≤ k < 4.

  • Member function void set(E item, int... indices) that stores item into the array location specified by indices.

    • The indices parameters are subject to the same rules as in the get function. E.g. the same exceptions are thrown in case of wrong number of indices or an illegal index.

  • member function int[] getDimensions() that returns an array containing the dimension sizes.

    • The returned array has size N and the value at index i tells the size of dimension (``i``+1).

    • NdArray probably maintains this kind of an array internally in order to know its dimension sizes. But note: do not return this kind of internal array directly! Create and return a new array because otherwise the function caller could corrupt internal data by modifying the array.

  • Implements the interface Iterable<E> (because the inherited AbstractCollection<E> does).

    • Iterater the array items in the same order as a typical way of using N nested loops where the outermost loop iterates the first dimension, the loop directly inside it iterates the second dimension, and so on.

    • Define a suitable iterator class that implements Iterator<E> (e.g. as a private inner class).

      • If you follow (and it is a good idea) the advice given below, the iterator can be very simple: it would suffice to maintain only information about the current iterator index in a one-dimensional table.

    • Implement a member function Iterator<E> iterator() that creates and returns an object of the above described iterator class.

Advice: one relatively simple approach to implement an N-dimensional array is to store the items into a one-dimensional array. The main challenge is then how to compute a mapping from N indices to an index of the internal one-dimensional array. The basic principle is described e.g. in this article (follow the “row-major” order described first in the article, as that makes iteration trivial). I do not recommend you to try to implement a “truly” multidimensional solution; it would make the task much more complicated.

Testing

You may test your implementation by using the test program given in the file NdArrayTest.java and the example output given in the files output1.txt, output2.txt, output3.txt and output4.txt. Place these files to the same directory with your code, compile the test program e.g. as javac *.java, and run the tests as java NdArrayTest 1, java NdArrayTest 2, java NdArrayTest 3 and java NdArrayTest 4. The expected outputs of these tests are given in the files output1.txt, output2.txt, output3.txt and output4.txt.

A+ presents the exercise submission form here.