# Arrays¶

This primer is a tutorial on Chapel’s rectangular arrays and domains. Other primers cover Chapel’s associative and sparse arrays, building on concepts introduced here.

Arrays in Chapel are specified using a square-bracketed expression
that specifies the array’s index set or *domain*, followed by the
array’s element type. Rectangular arrays are those whose indices
are integers or tuples of integers, bounded by a range in each
dimension, supporting multidimensional, rectilinear index sets.

## Declaring Arrays¶

Let’s start by declaring an `n`

-element array of 64-bit `real`

values (where `n`

defaults to 5):

```
config const n = 5;
var A: [1..n] real;
```

Like other variable types in Chapel, arrays are initialized so that
each element stores its default value. So our array of `real`

values
above will default to an array whose elements each store the value
0.0.

```
writeln("Initially, A is: ", A); // prints 0.0 for each array element
```

Arrays can also be declared using array literals. These are specified by enclosing a comma-separated list of expressions in square brackets. Unless otherwise specified, the domain of the new array variable will be 0-based, and the type of its elements will be that of the expressions if they all have the same type. If they do not, the array’s element type will be a type that can hold all of the values, if the compiler can determine that one exists.

```
var A2 = [-1.1, -2.2, -3.3, -4.4, -5.5];
writeln("Initially, A2 is: ", A2);
```

## Basic Array Indexing and Slicing¶

Arrays can be accessed using scalar index values of the appropriate type, using either parentheses or square brackets:

```
A[1] = 1.1;
A(2) = 2.2;
writeln("After assigning two elements, A is: ", A);
```

Arrays can also be accessed using ranges to refer to subsets of
array elements, or *sub-arrays*, using a technique called *slicing*:

```
A[2..4] = 3.3;
writeln("After assigning its interior values, A is: ", A);
writeln();
```

As with array indexing, either square brackets or parentheses can be used for array slicing:

```
writeln("A(2..4) is: ", A(2..4), "\n");
```

Further information on slicing can be found in the Slices Primer

## Multidimensional Arrays¶

Arrays can be multidimensional as well. For example, the following
declaration creates a 2D `n`

x `n`

array of `real`

values.

```
var B: [1..n, 1..n] real;
forall (i,j) in {1..n, 1..n} with (ref B) do
B[i,j] = i + j/10.0;
writeln("Initially, B is:\n", B, "\n");
```

## Loops over Arrays¶

An array’s elements can be iterated over using Chapel’s standard
loop forms like `for`

, `foreach`

, or `forall`

(see the
Loops Primer for details). These cause the
index variable to refer to an array element in each iteration. For
example, the following loop increments each of `B`

’s elements by
1, in parallel:

```
forall b in B do
b += 1;
writeln("After incrementing B's elements, B is:\n", B, "\n");
```

## Domains and Domain Queries¶

An array’s index set is referred to as a domain — a first-class
language concept that stores the set of indices used to access the
array. The arrays `A`

and `B`

above are respectively declared
over the anonymous domains `{1..n}`

and `{1..n, 1..n}`

, created
from the ranges specified within the array type’s square brackets.
Array `A2`

above will have the implicit domain `{0..4}`

to
represent the five values in its initializing expression.

The explicit `ref`

intent is required for `B`

in the example below
because `B`

is not modifed directly through the loop’s index variable (in
this case `i`

and `j`

).

An array’s domain can be queried using the `.domain`

method,
which returns a `const ref`

to the domain in question. For
example, here’s a loop that iterates over B’s indices in parallel
by querying its domain:

```
forall (i,j) in B.domain with (ref B) do
B[i,j] -= 1;
writeln("After decrementing B's elements, B is:\n", B, "\n");
```

Domains can also be queried when arrays are passed to routines, as
a means of associating a new `const ref`

identifier with the
domain for the routine’s duration. For example, the following
procedure queries the domain of its array argument `X`

, naming it
`D`

:

```
proc negateAndPrintArr(ref X: [?D] real) {
writeln("within negateAndPrintArr, D is: ", D, "\n");
forall (i,j) in D with (ref X) do
X[i,j] = -X[i,j];
writeln("after negating X within negateAndPrintArr, X is:\n", X, "\n");
}
negateAndPrintArr(B);
```

Arrays are passed to routines by constant (`const`

) by
default, which does not allow them to be modified within the routine.
The above procedure `negateAndPrintArr()`

must use a non-constant
reference intent (`ref`

) explicitly, so that its modifications of `X`

are both allowed and reflected in the actual argument `B`

:

```
writeln("After calling negateAndPrintArr, B is:\n", B, "\n");
```

Domains can also be declared and named. This has several advantages, including:

allowing multiple arrays to share a single domain

associating a logical name with an index set

amortizing overheads when storing multiple distributed arrays with the same indices

enabling compiler optimizations.

The following domain declaration defines a 2D rectangular domain
called `ProbSpace`

, which has the same size, shape, and index set
as `B`

above.

```
const ProbSpace = {1..n, 1..n};
```

Note that we declare the domain as being `const`

, indicating that
we will never change the set of indices it represents. Besides
indicating the programmer’s intent, this can enable key compiler
optimizations, and is therefore recommended whenever a domain’s
index set is known to be invariant.

We can then use that domain to declare some arrays…

```
var C, D, E: [ProbSpace] bool;
```

…and to iterate over their shared index set…

```
forall (i,j) in ProbSpace with (ref C) do
C[i,j] = (i+j) % 3 == 0;
writeln("After assigning C, its value is:\n", C, "\n");
```

An array need not be accessed using indices from the domain that
was used to declare it. For example, the following loop indexes
into `B`

using indices from `ProbSpace`

even though there is no
direct relationship between `B`

and `ProbSpace`

.

```
for (i,j) in ProbSpace do
B[i,j] = i + j/10.0;
writeln("B has been re-assigned to:\n", B, "\n");
```

When iterating over a multidimensional domain, the indices can be
expressed using a single tuple variable rather than destructuring
the tuple into its integer components. Similarly, multidimensional
array accesses can be written using tuple indices rather than
multiple integer arguments. In the following example, the index
variable `ij`

stores a 2-tuple of integers (`2*int`

or ```
(int,
int)
```

). Note the use of tuple indexing to access the individual
components of the 2-tuple `ij`

.

```
for ij in ProbSpace do
D[ij] = ij(0) == ij(1);
writeln("After assigning D, its value is:\n", D, "\n");
```

For further information on domains, see the Domain Primer.

## Whole-Array Assignment¶

Arrays of similar size and shape support whole-array assignment.

```
E = C;
writeln("After assigning C to E, E's value is:\n", E, "\n");
```

Whole-array assignment also permits a scalar value that is compatible with the array’s element type to be assigned to each element of the array:

```
E = true;
writeln("After being assigned 'true', E is:\n", E, "\n");
```

Whole-array assignment can also be used for arrays or sub-arrays whose index sets differ as long as they have the same number of dimensions and the same shape (number of elements per dimension).

```
var F, G: [ProbSpace] real;
F[2..n-1, 2..n-1] = B[1..n-2, 3..n];
writeln("After assigning a slice of B to a slice of F, F's value is:\n", F, "\n");
```

## More on Slicing¶

Arrays can also be sliced using unbounded ranges in which either
the low and/or high bounds are omitted. In this case, the missing
bounds are defined by the array’s bounds. For example, the
following statement assigns all rows of `G`

starting from row 2
using all rows of `B`

up to number `n-1`

. It assigns all columns
since no bounds are provided in the second dimension.

```
G[2.., ..] = B[..n-1, ..];
writeln("After assigning a slice of B to G, G's value is:\n", G, "\n");
```

Array slicing supports rank-change semantics when sliced using a
scalar value rather than a range. In the following assignment,
recall that `A`

was our initial 1-dimensional array. The slice
of `B`

takes all columns of row `n/2`

, treating it as a 1D array.

```
A = B[n/2, ..];
writeln("After being assigned a slice of B, A is:\n", A, "\n");
```

Domains can also be sliced. However, rather than having indexing semantics, domain slicing computes the intersection between the domain’s index set and the specified slice. Like array indexing and slicing, domain slicing can be written with either square brackets or parentheses.

```
writeln("ProbSpace[1..n-2, 3..] is: ", ProbSpace[1..n-2, 3..], "\n");
```

Domain variables and expressions can also be used to specify an array slice rather than using lists of ranges. For example:

```
const ProbSpaceSlice = ProbSpace[0..n+1, 3..];
writeln("B[ProbSpaceSlice] is:\n", B[ProbSpaceSlice], "\n");
```

## Resizing Arrays¶

Another advantage to declaring named domain variables is that their index sets can be reassigned. This results in a logical re-allocation of all arrays declared over that domain, preserving the array values for any indices that are preserved by the new domain’s value:

```
var VarDom = {1..n};
var VarArr: [VarDom] real = [i in VarDom] i;
writeln("Initially, VarArr = ", VarArr, "\n");
```

Now, if we reassign `VarDom`

, `VarArr`

will be reallocated with the
old values preserved and the new values initialized to the element
type’s default value.

```
VarDom = {1..2*n};
writeln("After doubling VarDom, VarArr = ", VarArr, "\n");
```

As mentioned, this reallocation preserves values according
to index, so if we extend the lower bound of the domain, the
non-zero values will still logically be associated with indices
`1..n`

:

```
VarDom = {-n+1..2*n};
writeln("After lowering VarDom's lower bound, VarArr = ", VarArr, "\n");
```

If the domain shrinks, values will be thrown away

```
VarDom = {2..n-1};
writeln("After shrinking VarDom, VarArr = ", VarArr, "\n");
```

One trick to reallocate an array without preserving any values is
to assign its domain variable a degenerate domain (e.g. `{1..0}`

)
and then assign it the new value:

```
VarDom = {1..0}; // empty the array such that no values need to be preserved
writeln("VarArr is now empty: ", VarArr, "\n");
VarDom = {1..n}; // re-assign the domain to establish the new indices
writeln("VarArr should now be reset: ", VarArr, "\n");
```

Note that querying an array’s domain via the `.domain`

method or
the function argument query syntax does not result in a domain that
can be reassigned since those forms return a `const ref`

. In
particular, we cannot do:

`B.domain = {1..2*n, 1..2*n};`

nor:

`proc foo(X: [?D]) { D = {1..2*n}; }`

Instead, to resize such arrays, their domains would need to be
named variables, and would need to be passed by `ref`

to any
subroutines wanting to resize the arrays.

An implication of this is that arrays declared using an anonymous
domain cannot be reallocated. So for our original array
declarations `A`

and `B`

, we have no way of reallocating them.
Arrays with constant domains provide the compiler with optimization
benefits, so this design supports a common case efficiently.

## Array Fields / Storing Arrays in Objects¶

A record with an array field whose size is not known until initialization time can be declared as follows:

```
record wrapFixedArr {
const size: int;
var Arr: [1..size] real;
}
var RSmall = new wrapFixedArr(size=10);
var RLarge = new wrapFixedArr(size=1000);
writeln("Size of RSmall's FieldArr: ", RSmall.Arr.size);
writeln("Size of RLarge's FieldArr: ", RLarge.Arr.size);
```

Note that such a record does not support resizing the array since its domain is not a named variable. However, we could create such a record as follows:

```
record wrapDynArr {
var Inds = {1..0};
var Arr: [Inds] real;
}
var r: wrapDynArr;
writeln("Initial size of r: ", r.Arr.size);
r.Inds = {1..100};
writeln("New size of r: ", r.Arr.size);
```

Either of these approaches can be used to create records (or classes) with array fields where each instance of the type has a different array size.

Further information on records can be found in the Records Primer

## Arrays of Arrays¶

Arrays in Chapel can have arbitrary element types, such as numeric values, classes, or records. Arrays can also have array elements, and initial support for this is implemented in our compiler. For example:

```
var Y: [ProbSpace] [1..3] real;
forall (i,j) in ProbSpace with (ref Y) do
for k in 1..3 do
Y[i,j][k] = i*10 + j + k/10.0;
writeln("Y is:\n", Y);
```

In such array-of-array cases, our current implementation requires that the array elements all have the same index set. In the future, we expect to support skyline/jagged arrays, in which the inner array sizes can be a function of their indices. In particular, it is our intention to support arrays like these:

```
var Triangle: [row in 1..n] [1..row] real;
var HierArr: [lvl in 1..n] [1..2**lvl, 1..2**lvl] real;
```

For the time being, such cases must be implemented by wrapping the array elements in records, as shown above.