Como Trabalhar com Arrays Multidimensionais em Zig

Introdução

Arrays multidimensionais (matrizes) são fundamentais para representar tabelas, grades, imagens e dados científicos. Em Zig, matrizes são representadas como arrays de arrays com tamanho fixo em tempo de compilação, ou como slices alocados dinamicamente para tamanhos variáveis.

Nesta receita, você aprenderá a criar, acessar e manipular arrays multidimensionais em Zig.

Pré-requisitos

Matriz 2D com Tamanho Fixo

Arrays de arrays em tempo de compilação:

const std = @import("std");

pub fn main() !void {
    // Matriz 3x4 de inteiros
    const matriz = [3][4]i32{
        [_]i32{ 1, 2, 3, 4 },
        [_]i32{ 5, 6, 7, 8 },
        [_]i32{ 9, 10, 11, 12 },
    };

    // Acessar elemento [linha][coluna]
    std.debug.print("Elemento [1][2]: {d}\n", .{matriz[1][2]}); // 7

    // Imprimir toda a matriz
    std.debug.print("\nMatriz 3x4:\n", .{});
    for (matriz) |linha| {
        for (linha) |elem| {
            std.debug.print("{d:>4}", .{elem});
        }
        std.debug.print("\n", .{});
    }

    // Dimensões
    std.debug.print("\nLinhas: {d}, Colunas: {d}\n", .{ matriz.len, matriz[0].len });
}

Saída esperada

Elemento [1][2]: 7

Matriz 3x4:
   1   2   3   4
   5   6   7   8
   9  10  11  12

Linhas: 3, Colunas: 4

Matriz Mutável e Operações

const std = @import("std");

pub fn main() !void {
    // Criar matriz de zeros
    var grade: [5][5]u8 = .{.{0} ** 5} ** 5;

    // Desenhar um X na grade
    for (0..5) |i| {
        grade[i][i] = 1;
        grade[i][4 - i] = 1;
    }

    // Imprimir grade
    std.debug.print("Grade 5x5 com X:\n", .{});
    for (grade) |linha| {
        for (linha) |cell| {
            const ch: u8 = if (cell == 1) '#' else '.';
            std.debug.print("{c} ", .{ch});
        }
        std.debug.print("\n", .{});
    }
}

Saída esperada

Grade 5x5 com X:
# . . . #
. # . # .
. . # . .
. # . # .
# . . . #

Matriz Dinâmica com Alocador

Para matrizes com tamanho definido em tempo de execução:

const std = @import("std");

fn criarMatriz(allocator: std.mem.Allocator, linhas: usize, colunas: usize) ![][]f64 {
    const matriz = try allocator.alloc([]f64, linhas);
    errdefer allocator.free(matriz);

    var inicializado: usize = 0;
    errdefer {
        for (matriz[0..inicializado]) |linha| allocator.free(linha);
    }

    for (matriz) |*linha| {
        linha.* = try allocator.alloc(f64, colunas);
        inicializado += 1;
        // Inicializar com zeros
        @memset(linha.*, 0.0);
    }

    return matriz;
}

fn liberarMatriz(allocator: std.mem.Allocator, matriz: [][]f64) void {
    for (matriz) |linha| allocator.free(linha);
    allocator.free(matriz);
}

fn imprimirMatriz(matriz: [][]f64) void {
    for (matriz) |linha| {
        for (linha) |elem| {
            std.debug.print("{d:>8.2}", .{elem});
        }
        std.debug.print("\n", .{});
    }
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const linhas: usize = 3;
    const colunas: usize = 4;

    var matriz = try criarMatriz(allocator, linhas, colunas);
    defer liberarMatriz(allocator, matriz);

    // Preencher com valores
    for (matriz, 0..) |linha, i| {
        for (linha, 0..) |_, j| {
            matriz[i][j] = @as(f64, @floatFromInt(i * colunas + j + 1)) * 1.5;
        }
    }

    std.debug.print("Matriz dinâmica {d}x{d}:\n", .{ linhas, colunas });
    imprimirMatriz(matriz);
}

Multiplicação de Matrizes

const std = @import("std");

fn multiplicarMatrizes(
    comptime N: usize,
    comptime M: usize,
    comptime P: usize,
    a: [N][M]f64,
    b: [M][P]f64,
) [N][P]f64 {
    var resultado: [N][P]f64 = .{.{0.0} ** P} ** N;

    for (0..N) |i| {
        for (0..P) |j| {
            for (0..M) |k| {
                resultado[i][j] += a[i][k] * b[k][j];
            }
        }
    }

    return resultado;
}

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

    const B = [3][2]f64{
        .{ 7, 8 },
        .{ 9, 10 },
        .{ 11, 12 },
    };

    const C = multiplicarMatrizes(2, 3, 2, A, B);

    std.debug.print("A (2x3) x B (3x2) = C (2x2):\n", .{});
    for (C) |linha| {
        for (linha) |elem| {
            std.debug.print("{d:>8.1}", .{elem});
        }
        std.debug.print("\n", .{});
    }
}

Saída esperada

A (2x3) x B (3x2) = C (2x2):
    58.0    64.0
   139.0   154.0

Transpor Matriz

const std = @import("std");

fn transpor(comptime N: usize, comptime M: usize, matriz: [N][M]i32) [M][N]i32 {
    var resultado: [M][N]i32 = undefined;
    for (0..N) |i| {
        for (0..M) |j| {
            resultado[j][i] = matriz[i][j];
        }
    }
    return resultado;
}

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

    std.debug.print("Original (2x4):\n", .{});
    for (original) |linha| {
        for (linha) |elem| std.debug.print("{d:>4}", .{elem});
        std.debug.print("\n", .{});
    }

    const transposta = transpor(2, 4, original);

    std.debug.print("\nTransposta (4x2):\n", .{});
    for (transposta) |linha| {
        for (linha) |elem| std.debug.print("{d:>4}", .{elem});
        std.debug.print("\n", .{});
    }
}

Dicas e Boas Práticas

  1. Use arrays fixos quando possível: Arrays de tamanho fixo ficam na stack e são mais rápidos.

  2. Cuidado com a ordem dos índices: [linhas][colunas] – o primeiro índice é a linha, o segundo a coluna.

  3. errdefer para matrizes dinâmicas: Ao alocar linhas individualmente, use errdefer para liberar em caso de falha parcial.

  4. Considere arrays contíguos: Para melhor uso de cache em matrizes grandes, use um array 1D com cálculo de índice (i * colunas + j).

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.