← Volver al listado de tecnologías

Capítulo 4: Lector y Procesador CSV

Por: SiempreListo
pythonzigarchivosintermedio

Lector y Procesador CSV

Un procesador de archivos CSV que calcula estadísticas y transforma datos.

Conceptos que Aprenderás

Python

from pathlib import Path
from dataclasses import dataclass
from typing import Iterator
import csv

@dataclass
class Estadisticas:
    total: int = 0
    suma: float = 0
    minimo: float = float('inf')
    maximo: float = float('-inf')

    @property
    def promedio(self) -> float:
        return self.suma / self.total if self.total > 0 else 0

def leer_csv(archivo: Path) -> Iterator[dict]:
    with open(archivo, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for fila in reader:
            yield fila

def calcular_estadisticas(archivo: Path, columna: str) -> Estadisticas:
    stats = Estadisticas()
    for fila in leer_csv(archivo):
        try:
            valor = float(fila[columna])
            stats.total += 1
            stats.suma += valor
            stats.minimo = min(stats.minimo, valor)
            stats.maximo = max(stats.maximo, valor)
        except (KeyError, ValueError):
            continue
    return stats

def filtrar_csv(archivo: Path, columna: str, valor: str) -> list[dict]:
    return [fila for fila in leer_csv(archivo) if fila.get(columna) == valor]

def main():
    archivo = Path(input("Archivo CSV: "))
    if not archivo.exists():
        print("Archivo no encontrado")
        return

    # Mostrar columnas disponibles
    with open(archivo) as f:
        columnas = next(csv.reader(f))
    print(f"Columnas: {', '.join(columnas)}")

    columna = input("Columna para estadísticas: ")
    stats = calcular_estadisticas(archivo, columna)

    print(f"\nEstadísticas de '{columna}':")
    print(f"  Total: {stats.total}")
    print(f"  Suma: {stats.suma:.2f}")
    print(f"  Promedio: {stats.promedio:.2f}")
    print(f"  Mínimo: {stats.minimo:.2f}")
    print(f"  Máximo: {stats.maximo:.2f}")

if __name__ == "__main__":
    main()

Zig

const std = @import("std");

const Estadisticas = struct {
    total: u64 = 0,
    suma: f64 = 0,
    minimo: f64 = std.math.inf(f64),
    maximo: f64 = -std.math.inf(f64),

    fn promedio(self: Estadisticas) f64 {
        return if (self.total > 0) self.suma / @as(f64, @floatFromInt(self.total)) else 0;
    }
};

fn parsearLinea(linea: []const u8, allocator: std.mem.Allocator) !std.ArrayList([]const u8) {
    var campos = std.ArrayList([]const u8).init(allocator);
    var iter = std.mem.splitScalar(u8, linea, ',');
    while (iter.next()) |campo| {
        try campos.append(campo);
    }
    return campos;
}

fn buscarIndice(columnas: []const []const u8, nombre: []const u8) ?usize {
    for (columnas, 0..) |col, i| {
        if (std.mem.eql(u8, std.mem.trim(u8, col, " \r"), nombre)) return i;
    }
    return null;
}

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

    const stdout = std.io.getStdOut().writer();
    const stdin = std.io.getStdIn().reader();
    var buf: [256]u8 = undefined;

    try stdout.print("Archivo CSV: ", .{});
    const ruta = stdin.readUntilDelimiter(&buf, '\n') catch return;

    const archivo = std.fs.cwd().openFile(ruta, .{}) catch {
        try stdout.print("Archivo no encontrado\n", .{});
        return;
    };
    defer archivo.close();

    var reader = archivo.reader();
    var linea_buf: [4096]u8 = undefined;

    // Leer encabezados
    const encabezado = reader.readUntilDelimiter(&linea_buf, '\n') catch return;
    var columnas = try parsearLinea(encabezado, allocator);
    defer columnas.deinit();

    try stdout.print("Columnas: ", .{});
    for (columnas.items) |col| {
        try stdout.print("{s}, ", .{col});
    }
    try stdout.print("\n", .{});

    try stdout.print("Columna para estadísticas: ", .{});
    const col_nombre = stdin.readUntilDelimiter(&buf, '\n') catch return;
    const indice = buscarIndice(columnas.items, col_nombre) orelse {
        try stdout.print("Columna no encontrada\n", .{});
        return;
    };

    var stats = Estadisticas{};

    while (reader.readUntilDelimiter(&linea_buf, '\n')) |linea| {
        var campos = try parsearLinea(linea, allocator);
        defer campos.deinit();

        if (indice < campos.items.len) {
            const valor = std.fmt.parseFloat(f64, campos.items[indice]) catch continue;
            stats.total += 1;
            stats.suma += valor;
            stats.minimo = @min(stats.minimo, valor);
            stats.maximo = @max(stats.maximo, valor);
        }
    } else |_| {}

    try stdout.print("\nEstadísticas de '{s}':\n", .{col_nombre});
    try stdout.print("  Total: {d}\n", .{stats.total});
    try stdout.print("  Promedio: {d:.2}\n", .{stats.promedio()});
}

Diferencias Clave

AspectoPythonZig
CSVMódulo csvParsing manual
IteraciónGenerator yieldReader línea por línea
Infinitofloat(‘inf’)std.math.inf

Ejercicios

  1. Exporta resultados a nuevo CSV
  2. Soporta delimitadores personalizados
  3. Detecta y maneja valores vacíos

← Anterior | Siguiente →