Como Usar HashMap em Zig

Introdução

Um HashMap (também chamado de dicionário ou mapa) é uma estrutura de dados que associa chaves a valores, permitindo inserção, busca e remoção em tempo O(1) amortizado. Em Zig, a biblioteca padrão oferece std.HashMap, std.AutoHashMap e std.StringHashMap para diferentes tipos de chave.

Nesta receita, você aprenderá a usar essas estruturas para resolver problemas comuns do dia a dia.

Pré-requisitos

HashMap Básico com AutoHashMap

AutoHashMap detecta automaticamente a função de hash para o tipo da chave:

const std = @import("std");

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

    // Criar HashMap de i32 -> []const u8
    var mapa = std.AutoHashMap(i32, []const u8).init(allocator);
    defer mapa.deinit();

    // Inserir pares chave-valor
    try mapa.put(1, "um");
    try mapa.put(2, "dois");
    try mapa.put(3, "três");
    try mapa.put(4, "quatro");

    // Buscar um valor
    if (mapa.get(2)) |valor| {
        std.debug.print("Chave 2: {s}\n", .{valor});
    }

    // Verificar se chave existe
    std.debug.print("Contém 3? {}\n", .{mapa.contains(3)});
    std.debug.print("Contém 5? {}\n", .{mapa.contains(5)});

    // Tamanho
    std.debug.print("Total de entradas: {d}\n", .{mapa.count()});
}

Saída esperada

Chave 2: dois
Contém 3? true
Contém 5? false
Total de entradas: 4

StringHashMap para Chaves de Texto

StringHashMap é otimizado para chaves do tipo []const u8:

const std = @import("std");

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

    var capitais = std.StringHashMap([]const u8).init(allocator);
    defer capitais.deinit();

    try capitais.put("Brasil", "Brasília");
    try capitais.put("Argentina", "Buenos Aires");
    try capitais.put("Chile", "Santiago");
    try capitais.put("Peru", "Lima");
    try capitais.put("Colômbia", "Bogotá");

    // Buscar
    const paises = [_][]const u8{ "Brasil", "Chile", "México" };
    for (&paises) |pais| {
        if (capitais.get(pais)) |capital| {
            std.debug.print("{s} -> {s}\n", .{ pais, capital });
        } else {
            std.debug.print("{s} -> não encontrado\n", .{pais});
        }
    }
}

Iterar sobre um HashMap

const std = @import("std");

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

    var estoque = std.StringHashMap(u32).init(allocator);
    defer estoque.deinit();

    try estoque.put("Teclado", 45);
    try estoque.put("Mouse", 120);
    try estoque.put("Monitor", 15);
    try estoque.put("Headset", 30);

    // Iterar sobre todas as entradas
    std.debug.print("=== Estoque ===\n", .{});
    var it = estoque.iterator();
    while (it.next()) |entry| {
        std.debug.print("  {s}: {d} unidades\n", .{ entry.key_ptr.*, entry.value_ptr.* });
    }

    // Iterar apenas sobre as chaves
    std.debug.print("\nProdutos: ", .{});
    var kit = estoque.keyIterator();
    while (kit.next()) |key_ptr| {
        std.debug.print("{s} ", .{key_ptr.*});
    }
    std.debug.print("\n", .{});
}

Atualizar e Remover Entradas

const std = @import("std");

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

    var pontuacao = std.StringHashMap(u32).init(allocator);
    defer pontuacao.deinit();

    try pontuacao.put("Alice", 100);
    try pontuacao.put("Bob", 85);
    try pontuacao.put("Carlos", 90);

    // Atualizar valor existente (put sobrescreve)
    try pontuacao.put("Bob", 95);

    // getOrPut: obtém existente ou insere novo
    const result = try pontuacao.getOrPut("Diana");
    if (!result.found_existing) {
        result.value_ptr.* = 0; // Valor padrão para nova entrada
    }
    std.debug.print("Diana: {d}\n", .{result.value_ptr.*});

    // Remover uma entrada
    const removido = pontuacao.fetchRemove("Carlos");
    if (removido) |kv| {
        std.debug.print("Removido Carlos com pontuação {d}\n", .{kv.value});
    }

    // Listar resultado final
    std.debug.print("\nPontuação final:\n", .{});
    var it = pontuacao.iterator();
    while (it.next()) |entry| {
        std.debug.print("  {s}: {d}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
    }
}

Exemplo Prático: Contar Frequência de Palavras

const std = @import("std");

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

    const texto = "zig é rápido zig é seguro zig é incrível zig zig zig";

    var frequencia = std.StringHashMap(u32).init(allocator);
    defer frequencia.deinit();

    // Contar palavras
    var palavras = std.mem.tokenizeAny(u8, texto, " ");
    while (palavras.next()) |palavra| {
        const result = try frequencia.getOrPut(palavra);
        if (result.found_existing) {
            result.value_ptr.* += 1;
        } else {
            result.value_ptr.* = 1;
        }
    }

    // Exibir frequência
    std.debug.print("Frequência de palavras:\n", .{});
    var it = frequencia.iterator();
    while (it.next()) |entry| {
        std.debug.print("  \"{s}\": {d}x\n", .{ entry.key_ptr.*, entry.value_ptr.* });
    }
    std.debug.print("Palavras distintas: {d}\n", .{frequencia.count()});
}

HashMap com Valores Complexos

const std = @import("std");

const Produto = struct {
    nome: []const u8,
    preco: f64,
    estoque: u32,
};

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

    var catalogo = std.StringHashMap(Produto).init(allocator);
    defer catalogo.deinit();

    try catalogo.put("SKU001", .{ .nome = "Teclado Mecânico", .preco = 299.90, .estoque = 45 });
    try catalogo.put("SKU002", .{ .nome = "Mouse Gamer", .preco = 159.90, .estoque = 120 });
    try catalogo.put("SKU003", .{ .nome = "Monitor 4K", .preco = 2499.00, .estoque = 8 });

    // Buscar produto
    if (catalogo.get("SKU002")) |produto| {
        std.debug.print("Produto: {s}\n", .{produto.nome});
        std.debug.print("Preço: R$ {d:.2}\n", .{produto.preco});
        std.debug.print("Estoque: {d}\n", .{produto.estoque});
    }

    // Calcular valor total do estoque
    var valor_total: f64 = 0;
    var it = catalogo.iterator();
    while (it.next()) |entry| {
        const p = entry.value_ptr.*;
        valor_total += p.preco * @as(f64, @floatFromInt(p.estoque));
    }
    std.debug.print("\nValor total em estoque: R$ {d:.2}\n", .{valor_total});
}

Dicas e Boas Práticas

  1. Escolha o tipo certo: Use StringHashMap para chaves de texto, AutoHashMap para tipos primitivos.

  2. Use getOrPut: Para padrões de “inserir se não existe”, getOrPut é mais eficiente que verificar e inserir separadamente.

  3. Cuidado com referências: As chaves e valores são copiados no HashMap. Strings ([]const u8) são ponteiros, então garanta que os dados originais permaneçam válidos.

  4. Pré-aloque com ensureTotalCapacity: Se você sabe quantas entradas terá, pré-alocar evita realocações durante a inserção.

  5. Sempre libere: Use defer mapa.deinit() logo após a criação.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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