Programming in D – Tutorial and Reference
Ali Çehreli

Other D Resources

Arrays

We have defined five variables in one of the exercises of the last chapter, and used them in certain calculations. The definitions of those variables were the following:

    double value_1;
    double value_2;
    double value_3;
    double value_4;
    double value_5;

This method of defining variables individually does not scale to cases where even more variables are needed. Imagine needing a thousand values; it is almost impossible to define a thousand variables from value_1 to value_1000.

Arrays are useful in such cases: the array feature allows us to define a single variable that stores multiple values together. Although simple, arrays are the most common data structure used to store a collection of values.

This chapter covers only some of the features of arrays. More features will be introduced later in the Slices and Other Array Features chapter.

Definition

The definition of array variables is very similar to the definition of normal variables. The only difference is that the number of values associated with the variable is specified in square brackets. We can contrast the two definitions as follows:

    int     singleValue;
    int[10] arrayOfTenValues;

The first line above is the definition of a variable which stores a single value, just like the variables that we have defined so far. The second line is the definition of a variable which stores ten consecutive values. In other words, it stores an array of ten integer values. You can also think of it as defining ten variables of the same type, or as defining an array, for short.

Accordingly, the equivalent of the five separate variables above can be defined as an array of five values using the following syntax:

    double[5] values;

That definition can be read as 5 double values. Note that I have chosen the name of the array variable as plural to avoid confusing it with a single-valued variable. Variables which only store a single value are called scalar variables.

In summary, the definition of an array variable consists of the type of the values, the number of values, and the name of the variable that refers to the array of values:

    type_name[value_count] variable_name;

The type of the values can also be a user-defined type. (We will see user-defined types later.) For example:

    // An array that holds the weather information of all
    // cities. Here, the bool values may mean
    //   false: overcast
    //   true : sunny
    bool[cityCount] weatherConditions;

    // An array that holds the weights of a hundred boxes
    double[100] boxWeights;

    // Information about the students of a school
    StudentInformation[studentCount] studentInformation;
Containers and elements

Data structures that bring elements of a certain type together are called containers. According to this definition, arrays are containers. For example, an array that holds the air temperatures of the days in July can bring 31 double values together and form a container of elements of type double.

The variables of a container are called elements. The number of elements of an array is called the length of the array.

Accessing the elements

In order to differentiate the variables in the exercise of the previous chapter, we had to append an underscore and a number to their names as in value_1. This is not possible nor necessary when a single array stores all the values under a single name. Instead, the elements are accessed by specifying the element number within square brackets:

    values[0]

That expression can be read as the element with the number 0 of the array named values. In other words, instead of typing value_1 one must type values[0] with arrays.

There are two important points worth stressing here:

Index

The number of an element is called its index and the act of accessing an element is called indexing.

An index need not be a constant value; the value of a variable can also be used as an index, making arrays even more useful. For example, the month can be determined by the value of the monthIndex variable below:

    writeln("This month has ", monthDays[monthIndex], " days.");

When the value of monthIndex is 2, the expression above would print the value of monthDays[2], the number of days in March.

Only the index values between zero and one less than the length of the array are valid. For example, the valid indexes of a three-element array are 0, 1, and 2. Accessing an array with an invalid index causes the program to be terminated with an error.

Arrays are containers where the elements are placed side by side in the computer's memory. For example, the elements of the array holding the number of days in each month can be shown like the following (assuming a year when February has 28 days):

  indexes →     0    1    2    3    4    5    6    7    8    9   10   11
 elements →  | 31 | 28 | 31 | 30 | 31 | 30 | 31 | 31 | 30 | 31 | 30 | 31 |

Note: The indexes above are for demonstration purposes only; they are not stored in the computer's memory.

The element at index 0 has the value 31 (number of days in January); the element at index 1 has the value of 28 (number of days in February), etc.

Fixed-length arrays vs. dynamic arrays

When the length of an array is specified when the program is written, that array is a fixed-length array. When the length can change during the execution of the program, that array is a dynamic array.

Both of the arrays that we have defined above are fixed-length arrays because their element counts are specified as 5 and 12 at the time when the program is written. The lengths of those arrays cannot be changed during the execution of the program. To change their lengths, the source code must be modified and the program must be recompiled.

Defining dynamic arrays is simpler than defining fixed-length arrays because omitting the length makes a dynamic array:

    int[] dynamicArray;

The length of such an array can increase or decrease during the execution of the program.

Fixed-length arrays are also known as static arrays.

Using .length to get or set the number of elements

Arrays have properties as well, of which we will see only .length here. .length returns the number of elements of the array:

    writeln("The array has ", array.length, " elements.");

Additionally, the length of dynamic arrays can be changed by assigning a value to this property:

    int[] array;            // initially empty
    array.length = 5;       // now has 5 elements
An array example

Let's now revisit the exercise with the five values and write it again by using an array:

import std.stdio;

void main() {
    // This variable is used as a loop counter
    int counter;

    // The definition of a fixed-length array of five
    // elements of type double
    double[5] values;

    // Reading the values in a loop
    while (counter < values.length) {
        write("Value ", counter + 1, ": ");
        readf(" %s", &values[counter]);
        ++counter;
    }

    writeln("Twice the values:");
    counter = 0;
    while (counter < values.length) {
        writeln(values[counter] * 2);
        ++counter;
    }

    // The loop that calculates the fifths of the values would
    // be written similarly
}

Observations: The value of counter determines how many times the loops are repeated (iterated). Iterating the loop while its value is less than values.length ensures that the loops are executed once per element. As the value of that variable is incremented at the end of each iteration, the values[counter] expression refers to the elements of the array one by one: values[0], values[1], etc.

To see how this program is better than the previous one, imagine needing to read 20 values. The program above would require a single change: replacing 5 with 20. On the other hand, a program that did not use an array would have to have 20 variable definitions. Furthermore, since you would be unable to use a loop to iterate the 20 values, you would also have to repeat several lines 20 times, one time for each single-valued variable.

Initializing the elements

Like every variable in D, the elements of arrays are automatically initialized. The initial value of the elements depends on the type of the elements: 0 for int, double.nan for double, etc.

All of the elements of the values array above are initialized to double.nan:

    double[5] values;     // elements are all double.nan

Obviously, the values of the elements can be changed later during the execution of the program. We have already seen this above when assigning to an element of an array:

    monthDays[11] = 31;

That also happened when reading a value from the input:

    readf(" %s", &values[counter]);

Sometimes the desired values of the elements are known at the time when the array is defined. In such cases, the initial values of the elements can be specified on the right-hand side of the assignment operator, within square brackets. Let's see this in a program that reads the number of the month from the user, and prints the number of days in that month:

import std.stdio;

void main() {
    // Assuming that February has 28 days
    int[12] monthDays =
        [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];

    write("Please enter the number of the month: ");
    int monthNumber;
    readf(" %s", &monthNumber);

    int index = monthNumber - 1;
    writeln("Month ", monthNumber, " has ",
            monthDays[index], " days.");
}

As you can see, the monthDays array is defined and initialized at the same time. Also note that the number of the month, which is in the range 1-12, is converted to a valid array index in the range 0-11. Any value that is entered outside of the 1-12 range would cause the program to be terminated with an error.

When initializing arrays, it is possible to use a single value on the right-hand side. In that case all of the elements of the array are initialized to that value:

    int[10] allOnes = 1;    // All of the elements are set to 1
Basic array operations

Arrays provide convenience operations that apply to all of their elements.

Copying fixed-length arrays

The assignment operator copies all of the elements from the right-hand side to the left-hand side:

    int[5] source = [ 10, 20, 30, 40, 50 ];
    int[5] destination;

    destination = source;

Note: The meaning of the assignment operation is completely different for dynamic arrays. We will see this in a later chapter.

Adding elements to dynamic arrays

The ~= operator adds new elements to the end of a dynamic array:

    int[] array;                // empty
    array ~= 7;                 // array is now equal to [7]
    array ~= 360;               // array is now equal to [7, 360]
    array ~= [ 30, 40 ];        // array is now equal to [7, 360, 30, 40]

It is not possible to add elements to fixed-length arrays:

    int[10] array;
    array ~= 7;                 // ← compilation ERROR
Combining arrays

The ~ operator creates a new array by combining two arrays. Its ~= counterpart combines the two arrays and assigns the result back to the left-hand side array:

import std.stdio;

void main() {
    int[10] first = 1;
    int[10] second = 2;
    int[] result;

    result = first ~ second;
    writeln(result.length);     // prints 20

    result ~= first;
    writeln(result.length);     // prints 30
}

The ~= operator cannot be used when the left-hand side array is a fixed-length array:

    int[20] result;
    // ...
    result ~= first;          // ← compilation ERROR

If the array sizes are not equal, the program is terminated with an error during assignment:

    int[10] first = 1;
    int[10] second = 2;
    int[21] result;

    result = first ~ second;
object.Error@(0): Array lengths don't match for copy: 20 != 21
Sorting the elements

std.algorithm.sort can sort the elements of many types of collections. In the case of integers, the elements get sorted from the smallest value to the greatest value. In order to use the sort() function, one must import the std.algorithm module first. (We will see functions in a later chapter.)

import std.stdio;
import std.algorithm;

void main() {
    int[] array = [ 4, 3, 1, 5, 2 ];
    sort(array);
    writeln(array);
}

The output:

[1, 2, 3, 4, 5]
Reversing the elements

std.algorithm.reverse reverses the elements in place (the first element becomes the last element, etc.):

import std.stdio;
import std.algorithm;

void main() {
    int[] array = [ 4, 3, 1, 5, 2 ];
    reverse(array);
    writeln(array);
}

The output:

[2, 5, 1, 3, 4]
Exercises
  1. Write a program that asks the user how many values will be entered and then reads all of them. Have the program sort the elements using sort() and then reverse the sorted elements using reverse().
  2. Write a program that reads numbers from the input, and prints the odd and even ones separately but in order. Treat the value -1 specially to determine the end of the numbers; do not process that value.

    For example, when the following numbers are entered,

    1 4 7 2 3 8 11 -1
    

    have the program print the following:

    1 3 7 11 2 4 8
    

    Hint: You may want to put the elements in separate arrays. You can determine whether a number is odd or even using the % (remainder) operator.

  3. The following is a program that does not work as expected. The program is written to read five numbers from the input and to place the squares of those numbers into an array. The program then attempts to print the squares to the output. Instead, the program terminates with an error.

    Fix the bugs of this program and make it work as expected:

    import std.stdio;
    
    void main() {
        int[5] squares;
    
        writeln("Please enter 5 numbers");
    
        int i = 0;
        while (i <= 5) {
            int number;
            write("Number ", i + 1, ": ");
            readf(" %s", &number);
    
            squares[i] = number * number;
            ++i;
        }
    
        writeln("=== The squares of the numbers ===");
        while (i <= squares.length) {
            write(squares[i], " ");
            ++i;
        }
    
        writeln();
    }