Ir al contenido principal

Manipulación de tablas en Lua

Las tablas son la estructura de datos fundamental en Lua, y dominar su manipulación es esencial para escribir código eficiente y elegante. Lua proporciona una librería estándar llamada table con un conjunto de funciones diseñadas específicamente para facilitar las operaciones más comunes sobre tablas. Estas funciones nos permiten insertar, eliminar, ordenar, concatenar y mover elementos de manera sencilla, sin necesidad de implementar estas operaciones desde cero.

En este artículo exploraremos en detalle cada una de las funciones de la librería table, comprendiendo no solo su sintaxis, sino también sus casos de uso prácticos y las mejores formas de aplicarlas en situaciones reales. Aprenderás a trabajar con listas de forma profesional, optimizando tu código y evitando errores comunes.

La librería table

Todas las funciones que veremos están contenidas en el módulo table de la librería estándar de Lua. Este módulo se carga automáticamente cuando ejecutas el intérprete, por lo que puedes usar sus funciones directamente sin necesidad de importar nada adicional. Las funciones están optimizadas para trabajar con la parte de array de las tablas (elementos con índices numéricos consecutivos comenzando en 1).

A continuación examinaremos las funciones más importantes:

  • table.concat() - concatenación de elementos
  • table.insert() - inserción de elementos
  • table.remove() - eliminación de elementos
  • table.move() - copia de elementos entre tablas
  • table.sort() - ordenación de elementos
  • table.unpack() - extracción de elementos
  • table.pack() - empaquetado de valores

Función table.concat

La función concat permite unir todos los elementos de una tabla en una única cadena de texto. Es especialmente útil cuando necesitas construir cadenas a partir de múltiples fragmentos, ya que es mucho más eficiente que concatenar con el operador .. en un bucle.

Signatura:

table.concat(lista [, separador [, inicio [, fin]]])

Parámetros:

  • lista: tabla cuyos elementos se concatenarán. Todos los elementos deben ser cadenas o números.
  • separador (opcional): cadena que se insertará entre cada elemento. Si no se especifica, se usa la cadena vacía por defecto.
  • inicio (opcional): índice del primer elemento a concatenar. Por defecto es 1.
  • fin (opcional): índice del último elemento a concatenar. Por defecto es #lista.

Retorna: una cadena de texto con todos los elementos concatenados.

Ejemplo básico:

frutas = {"manzana", "naranja", "platano", "uva"}

-- Concatenacion sin separador
print(table.concat(frutas))
-- Salida: manzananaranjaplátanouva

-- Concatenacion con separador
print(table.concat(frutas, ", "))
-- Salida: manzana, naranja, platano, uva

-- Concatenacion de un rango especifico
print(table.concat(frutas, " - ", 2, 3))
-- Salida: naranja - platano

Caso práctico - construcción de rutas:

ruta = {"usuario", "documentos", "proyectos", "lua"}

-- En Windows
rutaWindows = "C:\\" .. table.concat(ruta, "\\")
print(rutaWindows)
-- Salida: C:\usuario\documentos\proyectos\lua

-- En Unix/Linux
rutaUnix = "/" .. table.concat(ruta, "/")
print(rutaUnix)
-- Salida: /usuario/documentos/proyectos/lua

Nota importante: table.concat es significativamente más eficiente que concatenar cadenas con el operador .. dentro de un bucle, especialmente cuando trabajas con muchos elementos.

Función table.insert

La función insert añade un nuevo elemento a una tabla, desplazando los elementos existentes si es necesario para hacer espacio. Es la forma recomendada de añadir elementos a arrays en Lua.

Signatura:

table.insert(lista [, posicion], valor)

Parámetros:

  • lista: tabla en la que insertaremos el elemento.
  • posicion (opcional): índice donde se insertará el elemento. Si no se especifica, el elemento se añade al final de la lista.
  • valor: elemento a insertar en la tabla.

Ejemplo básico:

paises = {"Espana", "Francia", "Italia"}

-- Insertar al final (sin especificar posicion)
table.insert(paises, "Portugal")
print(table.concat(paises, ", "))
-- Salida: Espana, Francia, Italia, Portugal

-- Insertar en posicion especifica
table.insert(paises, 2, "Alemania")
print(table.concat(paises, ", "))
-- Salida: Espana, Alemania, Francia, Italia, Portugal

Caso práctico - cola de tareas:

tareas = {}

-- Anadir tareas al final de la cola
table.insert(tareas, {nombre = "Hacer backup", prioridad = "alta"})
table.insert(tareas, {nombre = "Actualizar sistema", prioridad = "media"})
table.insert(tareas, {nombre = "Limpiar archivos temporales", prioridad = "baja"})

-- Anadir tarea urgente al principio
table.insert(tareas, 1, {nombre = "Corregir error critico", prioridad = "urgente"})

-- Mostrar todas las tareas
for i, tarea in ipairs(tareas) do
    print(i .. ". " .. tarea.nombre .. " (" .. tarea.prioridad .. ")")
end
-- Salida:
-- 1. Corregir error critico (urgente)
-- 2. Hacer backup (alta)
-- 3. Actualizar sistema (media)
-- 4. Limpiar archivos temporales (baja)

Comportamiento importante: cuando insertas un elemento en una posición intermedia, todos los elementos desde esa posición en adelante se desplazan una posición hacia la derecha. Esto puede ser costoso en tablas muy grandes.

Función table.remove

La función remove elimina un elemento de la tabla y devuelve su valor. Los elementos posteriores se desplazan para llenar el hueco dejado.

Signatura:

table.remove(lista [, posicion])

Parámetros:

  • lista: tabla de la cual eliminaremos el elemento.
  • posicion (opcional): índice del elemento a eliminar. Si no se especifica, se elimina el último elemento de la lista.

Retorna: el valor del elemento eliminado.

Ejemplo básico:

colores = {"rojo", "verde", "azul", "amarillo", "negro"}

-- Eliminar elemento en posicion especifica
colorEliminado = table.remove(colores, 3)
print("Color eliminado: " .. colorEliminado)
-- Salida: Color eliminado: azul

print("Colores restantes: " .. table.concat(colores, ", "))
-- Salida: Colores restantes: rojo, verde, amarillo, negro

-- Eliminar el ultimo elemento (sin especificar posicion)
ultimoColor = table.remove(colores)
print("Ultimo color eliminado: " .. ultimoColor)
-- Salida: Ultimo color eliminado: negro

print("Colores finales: " .. table.concat(colores, ", "))
-- Salida: Colores finales: rojo, verde, amarillo

Caso práctico - implementación de una pila (stack):

pila = {}

-- Funcion para apilar (push)
function apilar(elemento)
    table.insert(pila, elemento)
end

-- Funcion para desapilar (pop)
function desapilar()
    if #pila > 0 then
        return table.remove(pila)
    else
        return nil
    end
end

-- Usar la pila
apilar("primero")
apilar("segundo")
apilar("tercero")

print(desapilar())  -- Salida: tercero
print(desapilar())  -- Salida: segundo
print(desapilar())  -- Salida: primero
print(desapilar())  -- Salida: nil

Nota de rendimiento: eliminar elementos del final de la tabla es muy eficiente. Sin embargo, eliminar elementos del principio o del medio requiere desplazar todos los elementos posteriores, lo cual puede ser lento en tablas grandes.

Función table.move

La función move copia elementos de una tabla a otra (o a una posición diferente dentro de la misma tabla). Esta función está disponible a partir de Lua 5.3.

Signatura:

table.move(tablaOrigen, desde, hasta, posicionDestino [, tablaDestino])

Parámetros:

  • tablaOrigen: tabla desde donde se copiarán los elementos.
  • desde: índice del primer elemento a copiar.
  • hasta: índice del último elemento a copiar (inclusive).
  • posicionDestino: índice donde comenzará la inserción en la tabla destino.
  • tablaDestino (opcional): tabla donde se insertarán los elementos copiados. Si se omite, los elementos se copian dentro de la misma tablaOrigen.

Retorna: la tabla destino.

Ejemplo básico - copiar entre tablas:

origen = {"a", "b", "c", "d", "e"}
destino = {"x", "y", "z"}

-- Copiar elementos 2 al 4 de origen, a partir de posicion 2 de destino
table.move(origen, 2, 4, 2, destino)

print("Destino: " .. table.concat(destino, ", "))
-- Salida: Destino: x, b, c, d

print("Origen: " .. table.concat(origen, ", "))
-- Salida: Origen: a, b, c, d, e (sin cambios)

Ejemplo - mover dentro de la misma tabla:

lista = {10, 20, 30, 40, 50}

-- Mover elementos 1-3 a posicion 3 (dentro de la misma tabla)
table.move(lista, 1, 3, 3)

print(table.concat(lista, ", "))
-- Salida: 10, 20, 10, 20, 30

Caso práctico - rotación de elementos:

function rotarIzquierda(tabla)
    if #tabla > 1 then
        -- Guardar el primer elemento
        local primero = tabla[1]
        -- Mover todos los elementos una posicion hacia la izquierda
        table.move(tabla, 2, #tabla, 1)
        -- Colocar el primer elemento al final
        tabla[#tabla] = primero
    end
end

numeros = {1, 2, 3, 4, 5}
rotarIzquierda(numeros)
print(table.concat(numeros, ", "))
-- Salida: 2, 3, 4, 5, 1

Función table.sort

La función sort ordena los elementos de una tabla in situ (modifica la tabla original). Por defecto, ordena en orden ascendente usando el operador <.

Signatura:

table.sort(lista [, funcionComparacion])

Parámetros:

  • lista: tabla a ordenar.
  • funcionComparacion (opcional): función que recibe dos elementos y retorna true si el primer elemento debe aparecer antes que el segundo. Si se omite, se usa el operador < de Lua.

Ejemplo básico:

numeros = {45, 12, 78, 23, 56, 34}

-- Ordenacion ascendente (por defecto)
table.sort(numeros)
print(table.concat(numeros, ", "))
-- Salida: 12, 23, 34, 45, 56, 78

-- Ordenacion descendente con funcion personalizada
table.sort(numeros, function(a, b) return a > b end)
print(table.concat(numeros, ", "))
-- Salida: 78, 56, 45, 34, 23, 12

Ejemplo con cadenas:

nombres = {"Carlos", "Ana", "Beatriz", "Daniel"}

-- Orden alfabetico
table.sort(nombres)
print(table.concat(nombres, ", "))
-- Salida: Ana, Beatriz, Carlos, Daniel

-- Orden alfabetico inverso
table.sort(nombres, function(a, b) return a > b end)
print(table.concat(nombres, ", "))
-- Salida: Daniel, Carlos, Beatriz, Ana

Caso práctico - ordenar tabla de objetos:

estudiantes = {
    {nombre = "Ana", nota = 85},
    {nombre = "Carlos", nota = 92},
    {nombre = "Beatriz", nota = 78},
    {nombre = "Daniel", nota = 95}
}

-- Ordenar por nota (descendente)
table.sort(estudiantes, function(a, b)
    return a.nota > b.nota
end)

print("Ranking de estudiantes:")
for i, estudiante in ipairs(estudiantes) do
    print(i .. ". " .. estudiante.nombre .. ": " .. estudiante.nota)
end
-- Salida:
-- Ranking de estudiantes:
-- 1. Daniel: 95
-- 2. Carlos: 92
-- 3. Ana: 85
-- 4. Beatriz: 78

Ordenación por múltiples criterios:

productos = {
    {nombre = "Laptop", categoria = "Electronica", precio = 800},
    {nombre = "Raton", categoria = "Electronica", precio = 25},
    {nombre = "Silla", categoria = "Muebles", precio = 150},
    {nombre = "Escritorio", categoria = "Muebles", precio = 300}
}

-- Ordenar primero por categoria, luego por precio
table.sort(productos, function(a, b)
    if a.categoria == b.categoria then
        return a.precio < b.precio
    else
        return a.categoria < b.categoria
    end
end)

for _, prod in ipairs(productos) do
    print(prod.categoria .. " - " .. prod.nombre .. ": " .. prod.precio .. " euros")
end
-- Salida:
-- Electronica - Raton: 25 euros
-- Electronica - Laptop: 800 euros
-- Muebles - Silla: 150 euros
-- Muebles - Escritorio: 300 euros

Nota importante: table.sort modifica la tabla original. Si necesitas mantener el orden original, haz una copia antes de ordenar.

Función table.unpack

La función unpack extrae y retorna los elementos de una tabla como valores individuales. Es la operación inversa de table.pack.

Signatura:

table.unpack(lista [, inicio [, fin]])

Parámetros:

  • lista: tabla de la cual extraer elementos.
  • inicio (opcional): índice del primer elemento a extraer. Por defecto es 1.
  • fin (opcional): índice del último elemento a extraer. Por defecto es #lista.

Retorna: los elementos de la tabla como valores separados.

Ejemplo básico:

coordenadas = {10, 20, 30}

-- Extraer todos los elementos
x, y, z = table.unpack(coordenadas)
print("x: " .. x .. ", y: " .. y .. ", z: " .. z)
-- Salida: x: 10, y: 20, z: 30

-- Extraer elementos especificos
primera, segunda = table.unpack(coordenadas, 1, 2)
print("Primera: " .. primera .. ", Segunda: " .. segunda)
-- Salida: Primera: 10, Segunda: 20

Caso práctico - paso de argumentos:

function calcularAreaRectangulo(ancho, alto)
    return ancho * alto
end

dimensiones = {15, 8}

-- Usar unpack para pasar los elementos como argumentos
area = calcularAreaRectangulo(table.unpack(dimensiones))
print("Area: " .. area)
-- Salida: Area: 120

Intercambio de variables:

valores = {5, 10}

-- Intercambiar usando unpack
valores[1], valores[2] = table.unpack(valores, 2, 1)
print(table.concat(valores, ", "))
-- Esto no funciona como esperamos. Mejor usar:

a, b = 5, 10
a, b = b, a  -- Forma correcta de intercambiar
print(a, b)
-- Salida: 10, 5

Función table.pack

La función pack crea una tabla a partir de una lista de valores. Es útil cuando trabajas con funciones que retornan múltiples valores.

Signatura:

table.pack(...)

Parámetros:

  • ...: número variable de argumentos a empaquetar.

Retorna: una tabla con todos los argumentos. La tabla también incluye un campo n con el número total de elementos (útil cuando algunos valores son nil).

Ejemplo básico:

function obtenerDatos()
    return "Juan", 25, "Madrid"
end

-- Empaquetar valores retornados en una tabla
datos = table.pack(obtenerDatos())

print("Elementos en la tabla: " .. datos.n)
print("Nombre: " .. datos[1])
print("Edad: " .. datos[2])
print("Ciudad: " .. datos[3])
-- Salida:
-- Elementos en la tabla: 3
-- Nombre: Juan
-- Edad: 25
-- Ciudad: Madrid

Caso práctico - función con argumentos variables:

function promediar(...)
    local valores = table.pack(...)
    local suma = 0
    
    for i = 1, valores.n do
        suma = suma + valores[i]
    end
    
    return suma / valores.n
end

print("Promedio: " .. promediar(10, 20, 30, 40, 50))
-- Salida: Promedio: 30.0

print("Promedio: " .. promediar(5, 15))
-- Salida: Promedio: 10.0

Manejo de valores nil:

-- table.pack preserva los nil
valores = table.pack(1, nil, 3, nil, 5)

print("Total de elementos: " .. valores.n)
-- Salida: Total de elementos: 5

-- Podemos iterar correctamente incluyendo los nil
for i = 1, valores.n do
    print("Elemento " .. i .. ": " .. tostring(valores[i]))
end
-- Salida:
-- Elemento 1: 1
-- Elemento 2: nil
-- Elemento 3: 3
-- Elemento 4: nil
-- Elemento 5: 5

Combinando funciones de table

Las funciones de la librería table son más poderosas cuando las combinas. Aquí algunos ejemplos prácticos:

Filtrar y ordenar datos:

function filtrarMayoresQue(lista, valor)
    local resultado = {}
    for _, elemento in ipairs(lista) do
        if elemento > valor then
            table.insert(resultado, elemento)
        end
    end
    table.sort(resultado)
    return resultado
end

numeros = {45, 12, 78, 23, 56, 34, 89, 15}
mayoresQue30 = filtrarMayoresQue(numeros, 30)

print("Numeros mayores que 30: " .. table.concat(mayoresQue30, ", "))
-- Salida: Numeros mayores que 30: 34, 45, 56, 78, 89

Gestión de historial:

historial = {}
maximoHistorial = 10

function agregarAlHistorial(comando)
    table.insert(historial, 1, comando)  -- Insertar al principio
    
    -- Mantener solo los ultimos 10 elementos
    while #historial > maximoHistorial do
        table.remove(historial)  -- Eliminar el ultimo
    end
end

-- Simular comandos
agregarAlHistorial("abrir archivo.txt")
agregarAlHistorial("buscar 'patron'")
agregarAlHistorial("guardar")

print("Historial reciente:")
for i, cmd in ipairs(historial) do
    print(i .. ". " .. cmd)
end
-- Salida:
-- Historial reciente:
-- 1. guardar
-- 2. buscar 'patron'
-- 3. abrir archivo.txt

Resumen

En este artículo hemos explorado a fondo la librería table de Lua, una herramienta fundamental para trabajar eficientemente con arrays y listas. Aprendiste cómo table.concat te permite unir elementos de forma eficiente, table.insert y table.remove facilitan la modificación dinámica de listas, table.move permite copiar rangos de elementos, table.sort ordena tus datos con criterios personalizables, y table.pack/table.unpack convierten entre listas de valores y tablas.

Dominar estas funciones te permitirá escribir código más limpio, eficiente y profesional. Son las herramientas básicas que todo programador de Lua debe tener en su arsenal para manipular colecciones de datos de manera efectiva. En los próximos artículos exploraremos conceptos más avanzados como iteradores personalizados y técnicas de programación funcional con tablas.