Como Dividir (Split) Strings por Delimitador em Zig

Como Dividir (Split) Strings por Delimitador em Zig

Dividir strings por um delimitador é uma operação fundamental para parsing de dados, processamento de texto e análise de formatos como CSV. Zig oferece várias funções na biblioteca padrão para fazer isso de forma eficiente e sem alocação de memória.

Split vs Tokenize

Zig oferece duas famílias de funções para dividir strings:

  • split: mantém campos vazios quando delimitadores consecutivos são encontrados
  • tokenize: ignora campos vazios (semelhante ao comportamento de strtok em C)

Dividir por Caractere Único com splitScalar

A função std.mem.splitScalar divide uma string usando um único caractere como delimitador.

const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // Dividir por vírgula
    const csv = "João,25,São Paulo,Brasil";
    var iter = std.mem.splitScalar(u8, csv, ',');

    try stdout.print("Campos do CSV:\n", .{});
    while (iter.next()) |campo| {
        try stdout.print("  -> \"{s}\"\n", .{campo});
    }

    // Dividir caminho por /
    try stdout.print("\nPartes do caminho:\n", .{});
    const caminho = "/home/usuario/documentos/arquivo.txt";
    var iter2 = std.mem.splitScalar(u8, caminho, '/');
    while (iter2.next()) |parte| {
        try stdout.print("  -> \"{s}\"\n", .{parte});
    }
}

Saída esperada:

Campos do CSV:
  -> "João"
  -> "25"
  -> "São Paulo"
  -> "Brasil"

Partes do caminho:
  -> ""
  -> "home"
  -> "usuario"
  -> "documentos"
  -> "arquivo.txt"

Note que splitScalar preserva campos vazios (como o primeiro campo vazio antes de /home).

Dividir por Sequência de Caracteres

Use std.mem.splitSequence para dividir por uma sequência de múltiplos caracteres.

const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // Dividir por " -> "
    const fluxo = "início -> processamento -> validação -> fim";
    var iter = std.mem.splitSequence(u8, fluxo, " -> ");

    try stdout.print("Etapas do fluxo:\n", .{});
    while (iter.next()) |etapa| {
        try stdout.print("  [{s}]\n", .{etapa});
    }

    // Dividir por quebra de linha "\r\n"
    try stdout.print("\nLinhas:\n", .{});
    const texto = "Linha 1\r\nLinha 2\r\nLinha 3";
    var iter2 = std.mem.splitSequence(u8, texto, "\r\n");
    while (iter2.next()) |linha| {
        try stdout.print("  \"{s}\"\n", .{linha});
    }
}

Saída esperada:

Etapas do fluxo:
  [início]
  [processamento]
  [validação]
  [fim]

Linhas:
  "Linha 1"
  "Linha 2"
  "Linha 3"

Tokenizar Strings (Ignorar Vazios)

Use std.mem.tokenizeScalar quando quiser ignorar campos vazios resultantes de delimitadores consecutivos.

const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // Tokenizar por espaço (ignora espaços múltiplos)
    const texto = "  Olá    Mundo   Zig  ";
    var tokens = std.mem.tokenizeScalar(u8, texto, ' ');

    try stdout.print("Tokens (espaço):\n", .{});
    while (tokens.next()) |token| {
        try stdout.print("  \"{s}\"\n", .{token});
    }

    // Comparação: split mantém vazios
    try stdout.print("\nSplit (espaço) - com vazios:\n", .{});
    var split = std.mem.splitScalar(u8, texto, ' ');
    while (split.next()) |parte| {
        try stdout.print("  \"{s}\"\n", .{parte});
    }
}

Saída esperada:

Tokens (espaço):
  "Olá"
  "Mundo"
  "Zig"

Split (espaço) - com vazios:
  ""
  ""
  "Olá"
  ""
  ""
  ""
  "Mundo"
  ""
  ""
  "Zig"
  ""
  ""

Tokenizar por Múltiplos Delimitadores

Use std.mem.tokenizeAny para dividir por qualquer caractere de um conjunto.

const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // Tokenizar por múltiplos delimitadores (espaço, vírgula, ponto-e-vírgula)
    const entrada = "maçã, banana; laranja  uva,melão";
    var tokens = std.mem.tokenizeAny(u8, entrada, " ,;");

    try stdout.print("Frutas:\n", .{});
    while (tokens.next()) |fruta| {
        try stdout.print("  - {s}\n", .{fruta});
    }
}

Saída esperada:

Frutas:
  - maçã
  - banana
  - laranja
  - uva
  - melão

Coletar Resultados em ArrayList

Para armazenar os resultados do split em uma lista, use ArrayList.

const std = @import("std");

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

    const csv = "nome,idade,cidade,profissão";
    var iter = std.mem.splitScalar(u8, csv, ',');

    // Coletar em ArrayList
    var campos = std.ArrayList([]const u8).init(allocator);
    defer campos.deinit();

    while (iter.next()) |campo| {
        try campos.append(campo);
    }

    try stdout.print("Total de campos: {d}\n", .{campos.items.len});
    for (campos.items, 0..) |campo, i| {
        try stdout.print("  Campo {d}: \"{s}\"\n", .{ i, campo });
    }
}

Saída esperada:

Total de campos: 4
  Campo 0: "nome"
  Campo 1: "idade"
  Campo 2: "cidade"
  Campo 3: "profissão"

Exemplo Prático: Parser de CSV Simples

Veja um exemplo mais completo que faz parsing de um conteúdo CSV.

const std = @import("std");

const Pessoa = struct {
    nome: []const u8,
    idade: u32,
    cidade: []const u8,
};

fn parseLinhaCsv(linha: []const u8) !Pessoa {
    var campos = std.mem.splitScalar(u8, linha, ',');

    const nome = campos.next() orelse return error.CampoAusente;
    const idade_str = campos.next() orelse return error.CampoAusente;
    const cidade = campos.next() orelse return error.CampoAusente;

    const idade = try std.fmt.parseInt(u32, idade_str, 10);

    return Pessoa{
        .nome = nome,
        .idade = idade,
        .cidade = cidade,
    };
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    const dados =
        \\Ana,30,São Paulo
        \\Carlos,25,Rio de Janeiro
        \\Maria,35,Belo Horizonte
    ;

    var linhas = std.mem.splitScalar(u8, dados, '\n');

    try stdout.print("Pessoas cadastradas:\n", .{});
    while (linhas.next()) |linha| {
        if (linha.len == 0) continue;
        const pessoa = try parseLinhaCsv(linha);
        try stdout.print("  {s}, {d} anos, {s}\n", .{ pessoa.nome, pessoa.idade, pessoa.cidade });
    }
}

Saída esperada:

Pessoas cadastradas:
  Ana, 30 anos, São Paulo
  Carlos, 25 anos, Rio de Janeiro
  Maria, 35 anos, Belo Horizonte

Referência Rápida

FunçãoDelimitadorCampos Vazios
splitScalarCaractere únicoMantém
splitSequenceSequência de charsMantém
splitAnyQualquer de um conjuntoMantém
tokenizeScalarCaractere únicoRemove
tokenizeSequenceSequência de charsRemove
tokenizeAnyQualquer de um conjuntoRemove

Veja Também

Continue aprendendo Zig

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