# Multidimensional Arrays

### Multidimensional Arrays

A multidimensional array is an array whose elements are also arrays.

The most common example is a table, grid, or matrix.

```zig
const grid = [2][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
};
```

The type is:

```zig
[2][3]i32
```

Read it as:

```text
array of 2 rows, where each row is an array of 3 i32 values
```

So this array has:

```text
2 rows
3 columns per row
6 total numbers
```

#### Reading the Type

The type:

```zig
[2][3]i32
```

means:

```text
[2] of [3] of i32
```

The outer array has 2 elements. Each element is another array of 3 `i32` values.

You can think of it like this:

```text
grid
|
+-- row 0: [ 1, 2, 3 ]
+-- row 1: [ 4, 5, 6 ]
```

Each row has type:

```zig
[3]i32
```

The full grid has type:

```zig
[2][3]i32
```

#### Accessing Items

Use one index to get a row.

```zig
const row0 = grid[0];
```

Use two indexes to get one value.

```zig
const value = grid[1][2];
```

For this grid:

```zig
const grid = [2][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
};
```

the indexes are:

| Expression | Value |
|---|---:|
| `grid[0][0]` | 1 |
| `grid[0][1]` | 2 |
| `grid[0][2]` | 3 |
| `grid[1][0]` | 4 |
| `grid[1][1]` | 5 |
| `grid[1][2]` | 6 |

The first index chooses the row. The second index chooses the column.

```zig
grid[row][column]
```

Indexes start at `0`.

#### Changing Values

Use `var` if you want to change the array.

```zig
var grid = [2][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
};

grid[0][1] = 99;
```

Now the grid contains:

```text
1   99   3
4    5   6
```

The shape cannot change. It is still a `[2][3]i32`.

You can change values, but you cannot add a third row or a fourth column.

#### Looping Over Rows

A simple `for` loop over the grid gives you one row at a time.

```zig
const std = @import("std");

pub fn main() void {
    const grid = [2][3]i32{
        .{ 1, 2, 3 },
        .{ 4, 5, 6 },
    };

    for (grid) |row| {
        for (row) |value| {
            std.debug.print("{} ", .{value});
        }
        std.debug.print("\n", .{});
    }
}
```

Output:

```text
1 2 3
4 5 6
```

The outer loop visits each row. The inner loop visits each value inside that row.

#### Looping With Indexes

Sometimes you need row and column numbers.

```zig
const std = @import("std");

pub fn main() void {
    const grid = [2][3]i32{
        .{ 1, 2, 3 },
        .{ 4, 5, 6 },
    };

    for (grid, 0..) |row, r| {
        for (row, 0..) |value, c| {
            std.debug.print("grid[{}][{}] = {}\n", .{ r, c, value });
        }
    }
}
```

Output:

```text
grid[0][0] = 1
grid[0][1] = 2
grid[0][2] = 3
grid[1][0] = 4
grid[1][1] = 5
grid[1][2] = 6
```

The outer index `r` is the row index. The inner index `c` is the column index.

#### Arrays Are Still Values

A multidimensional array is still an array value.

If you assign it to another variable, Zig copies the whole array.

```zig
var a = [2][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
};

var b = a;

b[0][0] = 99;
```

Now:

```text
a[0][0] == 1
b[0][0] == 99
```

Changing `b` does not change `a`.

This is useful when you want an independent copy. It can be expensive if the array is large.

#### Passing Multidimensional Arrays to Functions

A function can accept a multidimensional array directly.

```zig
const std = @import("std");

fn printGrid(grid: [2][3]i32) void {
    for (grid) |row| {
        for (row) |value| {
            std.debug.print("{} ", .{value});
        }
        std.debug.print("\n", .{});
    }
}

pub fn main() void {
    const grid = [2][3]i32{
        .{ 1, 2, 3 },
        .{ 4, 5, 6 },
    };

    printGrid(grid);
}
```

This function accepts exactly a `[2][3]i32`.

This works:

```zig
printGrid(.{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
});
```

This does not work:

```zig
printGrid(.{
    .{ 1, 2 },
    .{ 3, 4 },
});
```

That second value has shape `[2][2]i32`, not `[2][3]i32`.

The shape is part of the type.

#### Passing by Pointer

Passing the whole array copies it. For small arrays this is fine. For large arrays, pass a pointer.

```zig
const std = @import("std");

fn printGrid(grid: *const [2][3]i32) void {
    for (grid.*) |row| {
        for (row) |value| {
            std.debug.print("{} ", .{value});
        }
        std.debug.print("\n", .{});
    }
}

pub fn main() void {
    const grid = [2][3]i32{
        .{ 1, 2, 3 },
        .{ 4, 5, 6 },
    };

    printGrid(&grid);
}
```

The parameter type is:

```zig
*const [2][3]i32
```

Read it as:

```text
pointer to a constant 2 by 3 array of i32 values
```

The function can read the grid but cannot modify it.

#### Passing a Mutable Pointer

To let a function modify the grid, use a mutable pointer.

```zig
fn clearGrid(grid: *[2][3]i32) void {
    for (grid) |*row| {
        for (row) |*value| {
            value.* = 0;
        }
    }
}
```

Usage:

```zig
var grid = [2][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
};

clearGrid(&grid);
```

After the call, every value is zero.

The `|*row|` syntax captures each row by pointer. The `|*value|` syntax captures each value by pointer. Then `value.* = 0` writes through the pointer.

For beginners, the main idea is this: to modify elements inside a loop, you need access to the elements themselves, not just copies of them.

#### Row-Major Layout

Zig stores nested fixed arrays in a direct, predictable layout.

For:

```zig
const grid = [2][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
};
```

the values are stored like this:

```text
1 2 3 4 5 6
```

The first row comes first, then the second row.

This layout is called row-major order.

That means `grid[0]` is stored before `grid[1]`.

This matters for performance. Programs usually run faster when they read memory in order.

Good:

```zig
for (grid) |row| {
    for (row) |value| {
        // reads values in memory order
    }
}
```

Less ideal:

```zig
var c: usize = 0;
while (c < 3) : (c += 1) {
    var r: usize = 0;
    while (r < 2) : (r += 1) {
        _ = grid[r][c];
    }
}
```

The second version reads by column. For small arrays, it does not matter. For large arrays, memory order can affect speed.

#### Three-Dimensional Arrays

You can nest more arrays.

```zig
const cube = [2][3][4]u8{
    .{
        .{ 1, 2, 3, 4 },
        .{ 5, 6, 7, 8 },
        .{ 9, 10, 11, 12 },
    },
    .{
        .{ 13, 14, 15, 16 },
        .{ 17, 18, 19, 20 },
        .{ 21, 22, 23, 24 },
    },
};
```

The type is:

```zig
[2][3][4]u8
```

Read it as:

```text
array of 2 layers
each layer has 3 rows
each row has 4 u8 values
```

You access one value with three indexes:

```zig
const x = cube[1][2][3];
```

That means:

```text
layer 1
row 2
column 3
```

For absolute beginners, two-dimensional arrays are enough for now. The same rule extends to more dimensions.

#### Fixed Shape vs Dynamic Shape

A multidimensional fixed array has a compile-time shape.

```zig
[2][3]i32
```

This means the number of rows and columns is known before the program runs.

This is useful for:

```text
small matrices
game boards with fixed size
lookup tables
pixel kernels
protocol tables
embedded buffers
compile-time data
```

For example, a 3 by 3 image filter kernel can be stored as:

```zig
const kernel = [3][3]f32{
    .{ 0, -1, 0 },
    .{ -1, 5, -1 },
    .{ 0, -1, 0 },
};
```

A chess board could be:

```zig
const Board = [8][8]Piece;
```

The fixed shape makes the code precise.

But if the size is only known at runtime, a fixed multidimensional array is not the right tool. You will usually use a flat allocation plus width and height values, or a slice of rows.

#### Flattened Arrays

Sometimes a flat array is better than a multidimensional one.

Instead of:

```zig
const grid = [2][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
};
```

you can store:

```zig
const width = 3;
const height = 2;

const grid = [_]i32{
    1, 2, 3,
    4, 5, 6,
};
```

To access row `r`, column `c`, compute the index:

```zig
const index = r * width + c;
const value = grid[index];
```

For a 2 by 3 grid:

| Row | Column | Flat Index |
|---:|---:|---:|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 0 | 2 | 2 |
| 1 | 0 | 3 |
| 1 | 1 | 4 |
| 1 | 2 | 5 |

This layout is common in graphics, games, numerical code, and parsers.

The multidimensional form is clearer:

```zig
grid[r][c]
```

The flat form is often more flexible:

```zig
grid[r * width + c]
```

#### Common Mistake: Uneven Rows

This is invalid:

```zig
const grid = [2][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5 },
};
```

The first row has 3 values. The second row has 2 values.

But the type says every row must be `[3]i32`.

Each row must have the same length.

#### Common Mistake: Reversing Rows and Columns

In:

```zig
const grid = [2][3]i32{
    .{ 1, 2, 3 },
    .{ 4, 5, 6 },
};
```

the type is `[2][3]i32`, not `[3][2]i32`.

There are 2 rows, and each row has 3 values.

This is valid:

```text
row 0: 1 2 3
row 1: 4 5 6
```

This mental model helps:

```zig
[rows][columns]T
```

So:

```zig
[2][3]i32
```

means:

```text
2 rows, 3 columns
```

#### Common Mistake: Expecting a Slice of Slices

A value of type:

```zig
[2][3]i32
```

is not the same as:

```zig
[][]i32
```

A fixed multidimensional array stores everything in one nested fixed structure.

A slice of slices is different. It stores a sequence of slices, and each inner slice may point somewhere else.

For beginners, keep this distinction simple:

```text
[2][3]i32  fixed shape, stored directly
[][]i32    dynamic rows, each row is a slice
```

They are useful for different jobs.

#### A Complete Example

```zig
const std = @import("std");

fn sumGrid(grid: *const [2][3]i32) i32 {
    var total: i32 = 0;

    for (grid.*) |row| {
        for (row) |value| {
            total += value;
        }
    }

    return total;
}

fn clearGrid(grid: *[2][3]i32) void {
    for (grid) |*row| {
        for (row) |*value| {
            value.* = 0;
        }
    }
}

pub fn main() void {
    var grid = [2][3]i32{
        .{ 1, 2, 3 },
        .{ 4, 5, 6 },
    };

    const total = sumGrid(&grid);
    std.debug.print("sum = {}\n", .{total});

    clearGrid(&grid);

    for (grid) |row| {
        for (row) |value| {
            std.debug.print("{} ", .{value});
        }
        std.debug.print("\n", .{});
    }
}
```

Output:

```text
sum = 21
0 0 0
0 0 0
```

This example shows the main ideas:

```text
use [2][3]i32 for a fixed 2 by 3 grid
pass *const [2][3]i32 to read without copying
pass *[2][3]i32 to modify without copying
loop over rows, then values
```

#### Summary

A multidimensional array is an array of arrays.

```zig
[2][3]i32
```

means an array of 2 rows, where each row contains 3 `i32` values.

Use:

```zig
grid[row][column]
```

to access one value.

The shape is part of the type. `[2][3]i32` and `[3][2]i32` are different types.

Multidimensional arrays are best when the shape is known at compile time. They are clear, direct, and stored predictably in memory.

