Como Filtrar Arrays e Slices em Zig

Introdução

Filtrar arrays – selecionar apenas os elementos que atendem a determinado critério – é uma operação fundamental em qualquer linguagem. Em Zig, não existe uma função filter embutida como em linguagens funcionais, mas é simples implementar filtragem usando loops e ArrayList.

Nesta receita, você aprenderá a filtrar arrays de formas eficientes e reutilizáveis.

Pré-requisitos

Filtragem Básica com Loop

A forma mais direta de filtrar:

const std = @import("std");

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

    const numeros = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };

    // Filtrar números pares
    var pares = std.ArrayList(i32).init(allocator);
    defer pares.deinit();

    for (numeros) |n| {
        if (@mod(n, 2) == 0) {
            try pares.append(n);
        }
    }

    std.debug.print("Pares: ", .{});
    for (pares.items) |n| std.debug.print("{d} ", .{n});
    std.debug.print("\n", .{});

    // Filtrar maiores que 7
    var grandes = std.ArrayList(i32).init(allocator);
    defer grandes.deinit();

    for (numeros) |n| {
        if (n > 7) {
            try grandes.append(n);
        }
    }

    std.debug.print("Maiores que 7: ", .{});
    for (grandes.items) |n| std.debug.print("{d} ", .{n});
    std.debug.print("\n", .{});
}

Saída esperada

Pares: 2 4 6 8 10 12
Maiores que 7: 8 9 10 11 12

Função de Filtro Genérica

Crie uma função reutilizável para filtrar qualquer tipo:

const std = @import("std");

fn filter(
    comptime T: type,
    allocator: std.mem.Allocator,
    items: []const T,
    comptime predicate: fn (T) bool,
) ![]T {
    var resultado = std.ArrayList(T).init(allocator);
    errdefer resultado.deinit();

    for (items) |item| {
        if (predicate(item)) {
            try resultado.append(item);
        }
    }

    return resultado.toOwnedSlice();
}

fn ehPositivo(n: i32) bool {
    return n > 0;
}

fn ehPar(n: i32) bool {
    return @mod(n, 2) == 0;
}

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

    const dados = [_]i32{ -5, 3, -1, 8, 0, -3, 12, 7, -2, 4 };

    const positivos = try filter(i32, allocator, &dados, ehPositivo);
    defer allocator.free(positivos);

    std.debug.print("Positivos: ", .{});
    for (positivos) |n| std.debug.print("{d} ", .{n});
    std.debug.print("\n", .{});

    const pares = try filter(i32, allocator, &dados, ehPar);
    defer allocator.free(pares);

    std.debug.print("Pares: ", .{});
    for (pares) |n| std.debug.print("{d} ", .{n});
    std.debug.print("\n", .{});
}

Filtrar Strings

Filtrar uma lista de strings por critério:

const std = @import("std");

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

    const palavras = [_][]const u8{
        "zig", "rust", "go", "c", "java", "python", "lua", "ruby", "zig-brasil",
    };

    // Filtrar palavras com mais de 3 caracteres
    var longas = std.ArrayList([]const u8).init(allocator);
    defer longas.deinit();

    for (&palavras) |palavra| {
        if (palavra.len > 3) {
            try longas.append(palavra);
        }
    }

    std.debug.print("Palavras com mais de 3 letras:\n", .{});
    for (longas.items) |p| std.debug.print("  - {s}\n", .{p});

    // Filtrar palavras que contêm "zig"
    var com_zig = std.ArrayList([]const u8).init(allocator);
    defer com_zig.deinit();

    for (&palavras) |palavra| {
        if (std.mem.indexOf(u8, palavra, "zig") != null) {
            try com_zig.append(palavra);
        }
    }

    std.debug.print("\nContêm \"zig\":\n", .{});
    for (com_zig.items) |p| std.debug.print("  - {s}\n", .{p});
}

Filtrar Structs

Filtrar uma coleção de structs por campo:

const std = @import("std");

const Produto = struct {
    nome: []const u8,
    preco: f64,
    em_estoque: bool,
};

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

    const produtos = [_]Produto{
        .{ .nome = "Teclado", .preco = 199.90, .em_estoque = true },
        .{ .nome = "Monitor", .preco = 1899.00, .em_estoque = false },
        .{ .nome = "Mouse", .preco = 89.90, .em_estoque = true },
        .{ .nome = "Headset", .preco = 349.90, .em_estoque = true },
        .{ .nome = "Webcam", .preco = 459.00, .em_estoque = false },
        .{ .nome = "Mousepad", .preco = 49.90, .em_estoque = true },
    };

    // Filtrar disponíveis e abaixo de R$ 300
    var acessiveis = std.ArrayList(Produto).init(allocator);
    defer acessiveis.deinit();

    for (&produtos) |produto| {
        if (produto.em_estoque and produto.preco < 300.0) {
            try acessiveis.append(produto);
        }
    }

    std.debug.print("Produtos acessíveis em estoque (< R$300):\n", .{});
    for (acessiveis.items) |p| {
        std.debug.print("  {s}: R$ {d:.2}\n", .{ p.nome, p.preco });
    }
    std.debug.print("Total: {d} produtos\n", .{acessiveis.items.len});
}

Saída esperada

Produtos acessíveis em estoque (< R$300):
  Teclado: R$ 199.90
  Mouse: R$ 89.90
  Mousepad: R$ 49.90
Total: 3 produtos

Filtrar In-Place (Sem Alocação Extra)

Quando você pode modificar o array original e quer evitar alocação:

const std = @import("std");

fn filterInPlace(comptime T: type, slice: []T, comptime predicate: fn (T) bool) []T {
    var write_idx: usize = 0;
    for (slice) |item| {
        if (predicate(item)) {
            slice[write_idx] = item;
            write_idx += 1;
        }
    }
    return slice[0..write_idx];
}

fn maiorQue5(n: i32) bool {
    return n > 5;
}

pub fn main() !void {
    var dados = [_]i32{ 1, 8, 3, 12, 5, 7, 2, 9, 4, 11 };

    const resultado = filterInPlace(i32, &dados, maiorQue5);

    std.debug.print("Maiores que 5: ", .{});
    for (resultado) |n| std.debug.print("{d} ", .{n});
    std.debug.print("\n", .{});
    std.debug.print("Quantidade: {d}\n", .{resultado.len});
}

Dicas e Boas Práticas

  1. Use errdefer ao construir resultados: Garanta que ArrayList seja liberada em caso de erro durante a construção.

  2. toOwnedSlice transfere propriedade: O chamador fica responsável por liberar a memória retornada.

  3. Filter in-place para performance: Quando possível, modifique o slice original para evitar alocações.

  4. Combine com ordenação: Filtre primeiro, depois ordene o resultado para melhor performance.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

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