Verificador de Palíndromo em Zig — Tutorial Passo a Passo
Neste tutorial, vamos construir um verificador de palíndromos robusto no terminal. Um palíndromo é uma palavra ou frase que se lê da mesma forma da esquerda para a direita e vice-versa (ex: “arara”, “ovo”, “socorram-me subi no onibus em marrocos”). Este projeto explora manipulação de strings, normalização de texto e algoritmos de comparação em Zig.
O Que Vamos Construir
Nosso verificador vai:
- Verificar se palavras, frases e números são palíndromos
- Ignorar espaços, pontuação e diferenças maiúscula/minúscula
- Lidar com caracteres acentuados do português (á, é, ã, ç, etc.)
- Encontrar o maior subpalíndromo em um texto
- Gerar palíndromos numéricos em um intervalo
Por Que Este Projeto?
Verificar palíndromos parece trivial, mas se torna interessante quando precisamos lidar com texto real em português. Acentos, espaços e pontuação precisam ser normalizados antes da comparação. Este é um ótimo exercício de processamento de texto em Zig, onde trabalhamos com bytes diretamente.
Pré-requisitos
- Zig 0.13+ instalado (guia de instalação)
- Familiaridade com slices e loops em Zig (fundamentos)
Passo 1: Estrutura do Projeto
mkdir verificador-palindromo
cd verificador-palindromo
zig init
Passo 2: Normalização de Texto
O maior desafio de palíndromos em português é a normalização. “Socorram-me” deve ser tratado como “socorramme”.
const std = @import("std");
const io = std.io;
const fmt = std.fmt;
const mem = std.mem;
/// Verifica se um byte ASCII é uma letra (a-z, A-Z).
fn ehLetra(c: u8) bool {
return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z');
}
/// Verifica se um byte é um dígito (0-9).
fn ehDigito(c: u8) bool {
return c >= '0' and c <= '9';
}
/// Converte uma letra maiúscula para minúscula.
fn paraMinuscula(c: u8) u8 {
if (c >= 'A' and c <= 'Z') return c + 32;
return c;
}
/// Normaliza texto para comparação de palíndromos.
/// Remove espaços, pontuação e converte para minúsculas.
/// Também trata caracteres UTF-8 acentuados comuns do português,
/// convertendo-os para seus equivalentes sem acento.
///
/// Retorna o texto normalizado no buffer fornecido.
fn normalizar(texto: []const u8, buf: []u8) []const u8 {
var pos: usize = 0;
var i: usize = 0;
while (i < texto.len and pos < buf.len) {
const c = texto[i];
// Caracteres ASCII normais
if (ehLetra(c)) {
buf[pos] = paraMinuscula(c);
pos += 1;
i += 1;
} else if (ehDigito(c)) {
buf[pos] = c;
pos += 1;
i += 1;
} else if (c == 0xC3 and i + 1 < texto.len) {
// Sequências UTF-8 de 2 bytes para acentos latinos
// 0xC3 seguido do segundo byte indica acentuação
const next = texto[i + 1];
const mapeado: ?u8 = switch (next) {
0xA0, 0xA1, 0xA2, 0xA3, 0xA4 => 'a', // à á â ã ä
0x80, 0x81, 0x82, 0x83, 0x84 => 'a', // À Á Â Ã Ä
0xA8, 0xA9, 0xAA, 0xAB => 'e', // è é ê ë
0x88, 0x89, 0x8A, 0x8B => 'e', // È É Ê Ë
0xAC, 0xAD, 0xAE, 0xAF => 'i', // ì í î ï
0x8C, 0x8D, 0x8E, 0x8F => 'i', // Ì Í Î Ï
0xB2, 0xB3, 0xB4, 0xB5, 0xB6 => 'o', // ò ó ô õ ö
0x92, 0x93, 0x94, 0x95, 0x96 => 'o', // Ò Ó Ô Õ Ö
0xB9, 0xBA, 0xBB, 0xBC => 'u', // ù ú û ü
0x99, 0x9A, 0x9B, 0x9C => 'u', // Ù Ú Û Ü
0xA7 => 'c', // ç
0x87 => 'c', // Ç
else => null,
};
if (mapeado) |m| {
buf[pos] = m;
pos += 1;
}
i += 2;
} else {
// Pula caracteres não reconhecidos (espaços, pontuação, etc.)
i += 1;
}
}
return buf[0..pos];
}
Por que tratar UTF-8 manualmente? Zig não tem normalização Unicode na stdlib. Para nosso caso limitado (português), um mapeamento explícito dos acentos mais comuns é suficiente e não requer dependências externas. Para internacionalização completa, usaríamos uma biblioteca Unicode.
Passo 3: Verificação de Palíndromo
/// Resultado da verificação de palíndromo.
const ResultadoPalindromo = struct {
eh_palindromo: bool,
texto_original: []const u8,
texto_normalizado: [512]u8,
texto_normalizado_len: usize,
pub fn normalizado(self: *const ResultadoPalindromo) []const u8 {
return self.texto_normalizado[0..self.texto_normalizado_len];
}
};
/// Verifica se um texto (já normalizado) é palíndromo.
/// Usa a abordagem de dois ponteiros — um do início, outro do fim.
/// Complexidade O(n/2) em tempo, O(1) em espaço adicional.
fn ehPalindromo(texto: []const u8) bool {
if (texto.len <= 1) return true;
var esquerda: usize = 0;
var direita: usize = texto.len - 1;
while (esquerda < direita) {
if (texto[esquerda] != texto[direita]) return false;
esquerda += 1;
direita -= 1;
}
return true;
}
/// Verifica um texto completo, normalizando antes.
fn verificar(texto: []const u8) ResultadoPalindromo {
var resultado = ResultadoPalindromo{
.eh_palindromo = false,
.texto_original = texto,
.texto_normalizado = undefined,
.texto_normalizado_len = 0,
};
const norm = normalizar(texto, &resultado.texto_normalizado);
resultado.texto_normalizado_len = norm.len;
resultado.eh_palindromo = ehPalindromo(norm);
return resultado;
}
Passo 4: Maior Subpalíndromo
Encontrar o maior subpalíndromo é um problema clássico de programação. Usamos a abordagem de expansão a partir do centro.
/// Encontra o maior subpalíndromo em um texto normalizado.
/// Usa a técnica de expandir a partir de cada posição central.
/// Complexidade: O(n²) no pior caso.
fn maiorSubpalindromo(texto: []const u8) struct { inicio: usize, fim: usize } {
if (texto.len <= 1) return .{ .inicio = 0, .fim = texto.len };
var melhor_inicio: usize = 0;
var melhor_len: usize = 1;
// Tenta cada posição como centro
var centro: usize = 0;
while (centro < texto.len) : (centro += 1) {
// Palíndromos de comprimento ímpar (centro em um caractere)
var esq: usize = centro;
var dir: usize = centro;
while (esq > 0 and dir < texto.len - 1 and texto[esq - 1] == texto[dir + 1]) {
esq -= 1;
dir += 1;
}
const len = dir - esq + 1;
if (len > melhor_len) {
melhor_inicio = esq;
melhor_len = len;
}
// Palíndromos de comprimento par (centro entre dois caracteres)
if (centro + 1 < texto.len and texto[centro] == texto[centro + 1]) {
esq = centro;
dir = centro + 1;
while (esq > 0 and dir < texto.len - 1 and texto[esq - 1] == texto[dir + 1]) {
esq -= 1;
dir += 1;
}
const len2 = dir - esq + 1;
if (len2 > melhor_len) {
melhor_inicio = esq;
melhor_len = len2;
}
}
}
return .{ .inicio = melhor_inicio, .fim = melhor_inicio + melhor_len };
}
Passo 5: Palíndromos Numéricos
/// Verifica se um número é palíndromo.
fn ehPalindromoNumerico(n: u64) bool {
var buf: [20]u8 = undefined;
const texto = fmt.bufPrint(&buf, "{d}", .{n}) catch return false;
return ehPalindromo(texto);
}
/// Encontra palíndromos numéricos em um intervalo.
fn encontrarPalindromosNumericos(
inicio: u64,
fim: u64,
resultados: []u64,
) usize {
var count: usize = 0;
var n = inicio;
while (n <= fim and count < resultados.len) : (n += 1) {
if (ehPalindromoNumerico(n)) {
resultados[count] = n;
count += 1;
}
}
return count;
}
Passo 6: Interface CLI
/// Exibe o resultado formatado.
fn exibirResultado(resultado: ResultadoPalindromo, writer: anytype) !void {
const reset = "\x1b[0m";
try writer.print("\n Texto original: \"{s}\"\n", .{resultado.texto_original});
try writer.print(" Texto normalizado: \"{s}\"\n", .{resultado.normalizado()});
if (resultado.eh_palindromo) {
try writer.print(" Resultado: \x1b[32mE palindromo!\x1b[0m\n", .{});
// Mostrar a leitura invertida
const norm = resultado.normalizado();
try writer.print(" Invertido: \"", .{});
var i: usize = norm.len;
while (i > 0) {
i -= 1;
try writer.print("{c}", .{norm[i]});
}
try writer.print("\"\n", .{});
} else {
try writer.print(" Resultado: \x1b[31mNao e palindromo.{s}\n", .{reset});
}
}
/// Exemplos famosos de palíndromos em português.
const exemplos_palindromos = [_][]const u8{
"arara",
"ovo",
"reviver",
"radar",
"anilina",
"Socorram-me, subi no onibus em Marrocos",
"A base do teto desaba",
"Roma me tem amor",
"Anotaram a data da maratona",
};
pub fn main() !void {
const stdout = io.getStdOut().writer();
const stdin = io.getStdIn().reader();
try stdout.print(
\\
\\ ==========================================
\\ VERIFICADOR DE PALINDROMOS - Zig
\\ ==========================================
\\
, .{});
var buf: [512]u8 = undefined;
while (true) {
try stdout.print(
\\
\\ [1] Verificar texto
\\ [2] Encontrar maior subpalindromo
\\ [3] Palindromos numericos
\\ [4] Ver exemplos famosos
\\ [5] Sair
\\
\\ Opcao:
, .{});
const opcao_raw = stdin.readUntilDelimiterOrEof(&buf, '\n') catch continue orelse break;
const opcao = mem.trim(u8, opcao_raw, " \t\r\n");
if (mem.eql(u8, opcao, "5")) break;
if (mem.eql(u8, opcao, "1")) {
try stdout.print("\n Digite o texto: ", .{});
const texto_raw = stdin.readUntilDelimiterOrEof(&buf, '\n') catch continue orelse continue;
const texto = mem.trim(u8, texto_raw, " \t\r\n");
if (texto.len == 0) {
try stdout.print(" Texto vazio.\n", .{});
continue;
}
const resultado = verificar(texto);
try exibirResultado(resultado, stdout);
} else if (mem.eql(u8, opcao, "2")) {
try stdout.print("\n Digite o texto: ", .{});
const texto_raw = stdin.readUntilDelimiterOrEof(&buf, '\n') catch continue orelse continue;
const texto = mem.trim(u8, texto_raw, " \t\r\n");
var norm_buf: [512]u8 = undefined;
const norm = normalizar(texto, &norm_buf);
const sub = maiorSubpalindromo(norm);
try stdout.print("\n Texto normalizado: \"{s}\"\n", .{norm});
try stdout.print(" Maior subpalindromo: \"{s}\" (tamanho: {d})\n", .{
norm[sub.inicio..sub.fim], sub.fim - sub.inicio,
});
} else if (mem.eql(u8, opcao, "3")) {
try stdout.print("\n Inicio do intervalo: ", .{});
const ini_raw = stdin.readUntilDelimiterOrEof(&buf, '\n') catch continue orelse continue;
const inicio = fmt.parseInt(u64, mem.trim(u8, ini_raw, " \t\r\n"), 10) catch {
try stdout.print(" Numero invalido.\n", .{});
continue;
};
try stdout.print(" Fim do intervalo: ", .{});
const fim_raw = stdin.readUntilDelimiterOrEof(&buf, '\n') catch continue orelse continue;
const fim = fmt.parseInt(u64, mem.trim(u8, fim_raw, " \t\r\n"), 10) catch {
try stdout.print(" Numero invalido.\n", .{});
continue;
};
if (fim < inicio or fim - inicio > 10000) {
try stdout.print(" Intervalo invalido (max 10000).\n", .{});
continue;
}
var resultados: [200]u64 = undefined;
const count = encontrarPalindromosNumericos(inicio, fim, &resultados);
try stdout.print("\n Palindromos numericos entre {d} e {d}:\n ", .{ inicio, fim });
var i: usize = 0;
while (i < count) : (i += 1) {
try stdout.print("{d} ", .{resultados[i]});
if ((i + 1) % 10 == 0) try stdout.print("\n ", .{});
}
try stdout.print("\n Total: {d}\n", .{count});
} else if (mem.eql(u8, opcao, "4")) {
try stdout.print("\n Palindromos famosos em portugues:\n", .{});
for (exemplos_palindromos) |exemplo| {
const resultado = verificar(exemplo);
const simbolo: []const u8 = if (resultado.eh_palindromo) "[OK]" else "[--]";
try stdout.print(" {s} \"{s}\"\n", .{ simbolo, exemplo });
}
} else {
try stdout.print(" Opcao invalida.\n", .{});
}
}
try stdout.print("\n Ate logo!\n", .{});
}
Passo 7: Testes
test "palindromo simples" {
try std.testing.expect(ehPalindromo("arara"));
try std.testing.expect(ehPalindromo("ovo"));
try std.testing.expect(ehPalindromo("a"));
try std.testing.expect(ehPalindromo(""));
}
test "nao palindromo" {
try std.testing.expect(!ehPalindromo("zig"));
try std.testing.expect(!ehPalindromo("hello"));
}
test "normalizar remove espacos e pontuacao" {
var buf: [512]u8 = undefined;
const resultado = normalizar("Roma me tem amor!", &buf);
try std.testing.expectEqualStrings("romametemamor", resultado);
}
test "normalizar converte maiusculas" {
var buf: [512]u8 = undefined;
const resultado = normalizar("AaBb", &buf);
try std.testing.expectEqualStrings("aabb", resultado);
}
test "verificar frase palindroma" {
const resultado = verificar("Roma me tem amor");
try std.testing.expect(resultado.eh_palindromo);
}
test "palindromo numerico" {
try std.testing.expect(ehPalindromoNumerico(121));
try std.testing.expect(ehPalindromoNumerico(12321));
try std.testing.expect(!ehPalindromoNumerico(123));
}
test "maior subpalindromo" {
const sub = maiorSubpalindromo("abacaba");
try std.testing.expectEqualStrings("abacaba", "abacaba"[sub.inicio..sub.fim]);
}
test "encontrar palindromos numericos" {
var resultados: [200]u64 = undefined;
const count = encontrarPalindromosNumericos(10, 30, &resultados);
try std.testing.expectEqual(@as(usize, 2), count); // 11, 22
try std.testing.expectEqual(@as(u64, 11), resultados[0]);
try std.testing.expectEqual(@as(u64, 22), resultados[1]);
}
Compilando e Executando
zig build test
zig build run
Conceitos Aprendidos
- Algoritmo de dois ponteiros para verificação de palíndromos
- Normalização de texto (minúsculas, remoção de acentos e pontuação)
- Tratamento básico de sequências UTF-8 de 2 bytes
- Algoritmo de expansão a partir do centro para subpalíndromos
- Buffers fixos para processamento sem alocação
Próximos Passos
- Aprenda mais sobre manipulação de strings em Zig
- Explore o módulo std.unicode para tratamento Unicode completo
- Construa o próximo projeto: Analisador de Frequência