Sequences · Lesson 5.3

Array nested

Source
Build a two-dimensional grid as an array of arrays, int32[4][3], and walk it by rows and by columns.
You'll need: Array, Array repeat

An array's element type can be another array, and that is all a two-dimensional array is. A grid of three rows of four numbers is an array of three rows, where each row is an int32[4]. Boards, tables, images and spreadsheets all start out this way.

Built inside out, read outside in

The surprise is the order in the type. Each [n] wraps everything written before it, so the type is built inside out:

flowchart LR
    e["int32<br/>one number"] --> r["int32[4]<br/>one row of 4 numbers"]
    r --> g["int32[4][3]<br/>3 of those rows"]

Indexing reads the other way, outside in: grid[row] picks a row, and grid[row][column] picks a number within it.

let grid: int32[4][3] = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12]
];
column 0column 1column 2column 3
row 01234
row 15678
row 29101112

So grid[2][1] is row 2, column 1: 10. The outer length counts rows and a row's length counts columns:

PrintLine("{} rows of {} columns", grid.length, grid[0].length);

Walking by rows

for over the grid yields whole rows, and an inner for walks each row:

for row in grid {
    var sum: int32 = 0;
    for value in row {
        sum += value;
    }
    PrintLine("row {} {} {} {} adds up to {}", row[0], row[1], row[2], row[3], sum);
}

row is an ordinary int32[4], so it has a length, it can be indexed, and it can be walked like any other array.

Walking by columns

There is no "column" value to loop over — columns cut across the rows. Going down a column means holding the column fixed and stepping through the rows by index:

for column in 0..grid[0].length {
    var total: int32 = 0;
    for row in 0..grid.length {
        total += grid[row][column];
    }
    PrintLine("column {} adds up to {}", column, total);
}

Repeating a row

A repeated array nests too. [0; 3] is a row of three zeros, and [[0; 3]; 3] is three copies of that row — a blank board to fill in:

var board: int32[3][3] = [[0; 3]; 3];
for i in 0..board.length {
    board[i][i] = 1;
}

Setting board[i][i] for each i puts a 1 on the diagonal. Because each row is a separate copy, changing one row never touches another.

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// An array's element type can be another array, and that is all a two-dimensional array is. A
// grid of three rows of four numbers is an array of three rows, where each row is an `int32[4]`.
//
// The surprise is the order in the type. Each `[n]` wraps everything written before it, so the
// type is built inside out: `int32[4]` is one row, and `int32[4][3]` is three of those rows.
// Indexing reads the other way, outside in: `grid[row]` picks a row, and `grid[row][column]`
// picks a number within it.
import Io::PrintLine;

func Main() -> int {
    // Three rows, each a literal of four numbers.
    let grid: int32[4][3] = [
        [1, 2, 3, 4],
        [5, 6, 7, 8],
        [9, 10, 11, 12]
    ];

    // The outer length counts rows; a row's length counts columns.
    PrintLine("{} rows of {} columns", grid.length, grid[0].length);
    PrintLine("grid[2][1] is {}", grid[2][1]);

    // `for` over the grid yields whole rows, and an inner `for` walks each row.
    for row in grid {
        var sum: int32 = 0;
        for value in row {
            sum += value;
        }
        PrintLine("row {} {} {} {} adds up to {}", row[0], row[1], row[2], row[3], sum);
    }

    // Going down a column means holding the column fixed and stepping through the rows.
    for column in 0..grid[0].length {
        var total: int32 = 0;
        for row in 0..grid.length {
            total += grid[row][column];
        }
        PrintLine("column {} adds up to {}", column, total);
    }

    // A repeated array nests too: three copies of a row of three zeros, filled in afterwards.
    var board: int32[3][3] = [[0; 3]; 3];
    for i in 0..board.length {
        board[i][i] = 1;
    }
    for row in board {
        PrintLine("{} {} {}", row[0], row[1], row[2]);
    }
    return 0;
}

Run it

cd Examples/Sequences/ArrayNested
rux run
3 rows of 4 columns
grid[2][1] is 10
row 1 2 3 4 adds up to 10
row 5 6 7 8 adds up to 26
row 9 10 11 12 adds up to 42
column 0 adds up to 15
column 1 adds up to 18
column 2 adds up to 21
column 3 adds up to 24
1 0 0
0 1 0
0 0 1

Common mistakes

Writing the dimensions in reading order.
Three rows of four is int32[4][3], not int32[3][4]. The wrong order fails with error: cannot assign 'int[4][3]' to 'int32[3][4]' — the message shows the shape the literal really has.
One index with a comma.
grid[1, 2] is a syntax error: expected ']' to close the index expression before ','. Each level takes its own brackets: grid[1][2].
Rows of different lengths.
Every row has the same type, so a short row is refused: error: array element 2 has type 'int[3]', but the array's element type is 'int32[4]'.

Try it yourself

  1. Add up every number in grid with two nested for loops.
  2. Print grid with its rows and columns swapped, so that the first line reads 1 5 9.
  3. Build a multiplication table, var table: int32[10][10] = [[0; 10]; 10];, and fill it with (row + 1) * (column + 1). The indices are uint, so the product needs as int32.
  4. Change the type of grid to int32[3][4] and read the error.

Learn more