Ir al contenido principal

Operaciones avanzadas con cadenas

Las cadenas de texto son uno de los tipos de datos más utilizados en cualquier aplicación, desde la presentación de información al usuario hasta el procesamiento de datos de entrada. C# proporciona un conjunto robusto de herramientas para manipular cadenas de manera eficiente y elegante. En este artículo, exploraremos las operaciones avanzadas que nos permitirán trabajar con texto de forma profesional, incluyendo métodos de manipulación, formateo, búsqueda y análisis de patrones.

Dominar estas técnicas es fundamental para cualquier programador de C#, ya que las cadenas aparecen en prácticamente todos los aspectos del desarrollo de software: interfaces de usuario, procesamiento de archivos, comunicación con APIs, validación de datos y mucho más. Las operaciones que aprenderemos aquí nos proporcionarán las herramientas necesarias para manejar texto con precisión y eficiencia.

Formateo avanzado de cadenas

Interpolación de cadenas

La interpolación de cadenas, introducida en C# 6.0, proporciona una sintaxis limpia y legible para incrustar expresiones dentro de literales de cadena.

string nombre = "Carlos";
int edad = 25;
decimal salario = 3500.75m;

// Interpolación básica
string mensaje = $"Hola, soy {nombre} y tengo {edad} años";

// Con expresiones
string calculado = $"El próximo año tendré {edad + 1} años";

// Con formato
string formateado = $"Mi salario es {salario:C2}";

Console.WriteLine(mensaje);
Console.WriteLine(calculado);
Console.WriteLine(formateado);

Especificadores de formato

Especificador Descripción Ejemplo
C o c Moneda {valor:C} → "3.500,75 €"
D o d Decimal {numero:D5} → "00123"
F o f Punto fijo {decimal:F2} → "123,45"
N o n Numérico {numero:N} → "1.234,56"
P o p Porcentaje {decimal:P} → "12,34 %"
X o x Hexadecimal {numero:X} → "FF"
double valor = 1234.5678;
DateTime fecha = DateTime.Now;

// Diferentes formatos numéricos
Console.WriteLine($"Moneda: {valor:C}");
Console.WriteLine($"Punto fijo: {valor:F2}");
Console.WriteLine($"Científico: {valor:E2}");

// Formatos de fecha
Console.WriteLine($"Fecha corta: {fecha:d}");
Console.WriteLine($"Fecha larga: {fecha:D}");
Console.WriteLine($"Personalizada: {fecha:dd/MM/yyyy HH:mm}");

Métodos avanzados de manipulación

Métodos de búsqueda y análisis

string texto = "El rápido zorro marrón salta sobre el perro perezoso";

// Búsqueda de posiciones
int posicion = texto.IndexOf("zorro");
int ultimaPosicion = texto.LastIndexOf("el");
bool contiene = texto.Contains("rápido");

Console.WriteLine($"'zorro' está en la posición: {posicion}");
Console.WriteLine($"Última ocurrencia de 'el': {ultimaPosicion}");
Console.WriteLine($"¿Contiene 'rápido'?: {contiene}");

// Búsqueda con opciones de comparación
bool encuentraSinCase = texto.IndexOf("ZORRO", StringComparison.OrdinalIgnoreCase) >= 0;
Console.WriteLine($"Búsqueda sin distinguir mayúsculas: {encuentraSinCase}");

Análisis de contenido

string datos = "123,456.78";
string email = "usuario@dominio.com";
string espacios = "   texto con espacios   ";

// Análisis de contenido
bool esNumerico = datos.All(char.IsDigit);
bool tieneDigitos = datos.Any(char.IsDigit);
bool soloLetras = email.Take(email.IndexOf('@')).All(char.IsLetter);

Console.WriteLine($"¿Solo dígitos?: {esNumerico}");
Console.WriteLine($"¿Tiene dígitos?: {tieneDigitos}");
Console.WriteLine($"Usuario solo letras: {soloLetras}");

// Limpieza de espacios
string limpio = espacios.Trim();
string sinEspaciosIzq = espacios.TrimStart();
string sinEspaciosDer = espacios.TrimEnd();

Console.WriteLine($"Original: '{espacios}'");
Console.WriteLine($"Limpio: '{limpio}'");

StringBuilder para construcción eficiente

Cuando necesitamos realizar múltiples operaciones de concatenación o modificación de cadenas, StringBuilder ofrece un rendimiento significativamente mejor que las operaciones directas sobre string.

using System.Text;

// Construcción eficiente de cadenas largas
StringBuilder constructor = new StringBuilder();

constructor.AppendLine("=== REPORTE DIARIO ===");
constructor.AppendLine($"Fecha: {DateTime.Now:dd/MM/yyyy}");
constructor.AppendLine();

// Agregar contenido dinámicamente
for (int i = 1; i <= 5; i++)
{
    constructor.AppendLine($"Elemento {i}: Procesado correctamente");
}

constructor.AppendLine();
constructor.Append("Total de elementos procesados: ");
constructor.Append(5);

string reporte = constructor.ToString();
Console.WriteLine(reporte);

Métodos útiles de StringBuilder

StringBuilder sb = new StringBuilder("Texto inicial");

// Inserción en posiciones específicas
sb.Insert(0, ">>> ");
sb.Insert(sb.Length, " <<<");

// Reemplazo de contenido
sb.Replace("inicial", "modificado");

// Eliminación de caracteres
sb.Remove(0, 4); // Elimina los primeros 4 caracteres

// Control de capacidad para optimización
StringBuilder optimizado = new StringBuilder(1000); // Capacidad inicial
Console.WriteLine($"Capacidad actual: {optimizado.Capacity}");
Console.WriteLine($"Resultado final: {sb.ToString()}");

Expresiones regulares básicas

Las expresiones regulares proporcionan una herramienta poderosa para el análisis y manipulación de patrones en cadenas de texto.

using System.Text.RegularExpressions;

string textoConEmails = "Contactos: juan@empresa.com, maria@ejemplo.es, carlos@test.org";

// Patrón para emails
string patronEmail = @"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b";
Regex regexEmail = new Regex(patronEmail);

// Encontrar todas las coincidencias
MatchCollection emails = regexEmail.Matches(textoConEmails);

Console.WriteLine($"Se encontraron {emails.Count} emails:");
foreach (Match email in emails)
{
    Console.WriteLine($"- {email.Value}");
}

Patrones comunes

Patrón Descripción Expresión regular
Email Dirección de correo \b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b
Teléfono Número telefónico español ^[6-9]\d{8}$
DNI Documento nacional ^\d{8}[A-HJ-NP-TV-Z]$
Código postal CP español ^[0-5]\d{4}$
// Validación con expresiones regulares
string[] telefonos = { "612345678", "912345678", "512345678", "abc123456" };
Regex patronTelefono = new Regex(@"^[6-9]\d{8}$");

Console.WriteLine("Validación de teléfonos:");
foreach (string telefono in telefonos)
{
    bool esValido = patronTelefono.IsMatch(telefono);
    Console.WriteLine($"{telefono}: {(esValido ? "Válido" : "Inválido")}");
}

Manipulación avanzada y transformaciones

Operaciones de caso y formato

string textoMixto = "EsTE es Un TeXTO con ForMATO mixTO";

// Transformaciones de caso
string mayusculas = textoMixto.ToUpper();
string minusculas = textoMixto.ToLower();
string capitalizado = System.Globalization.CultureInfo.CurrentCulture.TextInfo
    .ToTitleCase(textoMixto.ToLower());

Console.WriteLine($"Original: {textoMixto}");
Console.WriteLine($"Mayúsculas: {mayusculas}");
Console.WriteLine($"Minúsculas: {minusculas}");
Console.WriteLine($"Capitalizado: {capitalizado}");

// Normalización para comparaciones
string texto1 = "café";
string texto2 = "CAFÉ";
bool sonIguales = string.Equals(texto1, texto2, StringComparison.OrdinalIgnoreCase);
Console.WriteLine($"¿'{texto1}' y '{texto2}' son iguales?: {sonIguales}");

División y unión avanzada

string csvData = "nombre,edad,ciudad,salario";
string textoMultilinea = @"Primera línea
Segunda línea con espacios extra   
Tercera línea
Línea vacía siguiente

Quinta línea";

// División con múltiples separadores
string[] columnas = csvData.Split(',');
string[] lineas = textoMultilinea.Split(new[] { '\r', '\n' }, 
    StringSplitOptions.RemoveEmptyEntries);

// Unión con separadores personalizados
string resultado = string.Join(" | ", columnas);
Console.WriteLine($"Columnas unidas: {resultado}");

// Procesamiento de líneas
Console.WriteLine("Líneas procesadas:");
foreach (string linea in lineas)
{
    string lineaLimpia = linea.Trim();
    if (!string.IsNullOrWhiteSpace(lineaLimpia))
    {
        Console.WriteLine($"- '{lineaLimpia}'");
    }
}

Codificación y conversiones

Trabajo con diferentes codificaciones

using System.Text;

string textoConAcentos = "Niño, corazón, programación";

// Diferentes codificaciones
byte[] utf8Bytes = Encoding.UTF8.GetBytes(textoConAcentos);
byte[] asciiBytes = Encoding.ASCII.GetBytes(textoConAcentos);
byte[] latin1Bytes = Encoding.Latin1.GetBytes(textoConAcentos);

Console.WriteLine($"Texto original: {textoConAcentos}");
Console.WriteLine($"UTF-8: {utf8Bytes.Length} bytes");
Console.WriteLine($"ASCII: {asciiBytes.Length} bytes");
Console.WriteLine($"Latin1: {latin1Bytes.Length} bytes");

// Conversión de vuelta
string desdeUtf8 = Encoding.UTF8.GetString(utf8Bytes);
string desdeAscii = Encoding.ASCII.GetString(asciiBytes);

Console.WriteLine($"Desde UTF-8: {desdeUtf8}");
Console.WriteLine($"Desde ASCII: {desdeAscii}");

Ejemplo práctico: Procesador de texto simple

using System.Text;
using System.Text.RegularExpressions;

public class ProcesadorTexto
{
    public static string LimpiarTexto(string texto)
    {
        if (string.IsNullOrWhiteSpace(texto))
            return string.Empty;
            
        // Eliminar múltiples espacios
        texto = Regex.Replace(texto, @"\s+", " ");
        
        // Limpiar espacios al inicio y final
        return texto.Trim();
    }
    
    public static Dictionary<string, int> ContarPalabras(string texto)
    {
        var contador = new Dictionary<string, int>();
        
        // Dividir en palabras, ignorando puntuación
        string[] palabras = Regex.Split(texto.ToLower(), @"\W+")
            .Where(p => !string.IsNullOrEmpty(p))
            .ToArray();
            
        foreach (string palabra in palabras)
        {
            if (contador.ContainsKey(palabra))
                contador[palabra]++;
            else
                contador[palabra] = 1;
        }
        
        return contador;
    }
    
    public static string GenerarResumen(string texto, int longitudMaxima = 100)
    {
        if (texto.Length <= longitudMaxima)
            return texto;
            
        // Buscar el último espacio antes del límite
        int ultimoEspacio = texto.LastIndexOf(' ', longitudMaxima);
        
        if (ultimoEspacio > 0)
            return texto.Substring(0, ultimoEspacio) + "...";
        else
            return texto.Substring(0, longitudMaxima) + "...";
    }
}

// Uso del procesador
string textoEjemplo = @"  Este    es un texto   de ejemplo para    
demostrar las capacidades del procesador de texto que hemos creado.  
Contiene múltiples espacios y saltos de línea innecesarios.  ";

string textoLimpio = ProcesadorTexto.LimpiarTexto(textoEjemplo);
var contadorPalabras = ProcesadorTexto.ContarPalabras(textoLimpio);
string resumen = ProcesadorTexto.GenerarResumen(textoLimpio, 50);

Console.WriteLine("=== ANÁLISIS DE TEXTO ===");
Console.WriteLine($"Original: '{textoEjemplo}'");
Console.WriteLine($"Limpio: '{textoLimpio}'");
Console.WriteLine($"Resumen: {resumen}");
Console.WriteLine($"Total de palabras únicas: {contadorPalabras.Count}");

Console.WriteLine("\nPalabras más frecuentes:");
foreach (var palabra in contadorPalabras.OrderByDescending(p => p.Value).Take(5))
{
    Console.WriteLine($"- '{palabra.Key}': {palabra.Value} veces");
}

Resumen

Las operaciones avanzadas con cadenas en C# nos proporcionan un conjunto completo de herramientas para manipular texto de manera eficiente y profesional. Hemos explorado desde técnicas básicas de formateo e interpolación hasta herramientas avanzadas como StringBuilder para construcción eficiente y expresiones regulares para análisis de patrones.

El dominio de estas técnicas es fundamental para crear aplicaciones robustas que manejen datos textuales con precisión. Las operaciones de búsqueda, transformación, validación y formato que hemos visto son esenciales en el desarrollo de interfaces de usuario, procesamiento de archivos, validación de datos y comunicación con servicios externos. En el siguiente artículo, comenzaremos a explorar la programación orientada a objetos, un paradigma que nos permitirá organizar y estructurar nuestro código de manera más elegante y mantenible.