Como desarrolladores, es común enfrentarse a la manipulación de estructuras de datos jerárquicas, como menús de navegación, organigramas o taxonomías de productos. Implementar manualmente operaciones como la búsqueda, el recorrido o la modificación de nodos puede ser una tarea ardua y propensa a errores. Este artículo presenta una librería eficaz y completa, @suzilong/tree, diseñada para simplificar radicalmente estas operaciones en JavaScript y TypeScript.
Características Fundamentales de la Librería
1. Conjunto Completo de Funcionalidades
- Recorridos: Soporte para estrategias de recorrido en profundidad (pre-orden, post-orden) y en anchura.
- Búsqueda: Capacidad para localizar nodos individualse, múltiples nodos que coincidan con un criterio y rutas completas a un nodo.
- Manipulación: Operaciones para añadir, eliminar, sustituir o mover nodos dentro del árbol.
- Conversión: Herramientas para transformar arreglos planos en árboles y viceversa, así como para mapear, filtrar y aplanar la estructura.
- Consultas: Métodos para obtener el conteo total de nodos, la cantidad de nodos hoja, la profundidad del árbol, y relaciones como ancestros, descendientes o hermanos.
- Utilidades Varias: Funciones para clonar árboles y realizar comparaciones (coincidencia total o parcial).
2. Principios de Diseño Clave
- Inmutabilidad: Todas las operaciones devuelven una nueva instancia del árbol, preservando la estructura original.
- Configurable: Permite la personalización del nombre del campo que define los hijos de un nodo.
- Ligera: Sin dependencias externas, desarrollada en TypeScript para garantizar la seguridad de tipos.
- Soporte para Bosques: Maneja estructuras con múltiples raíces, no solo árboles con una única raíz.
- Robustez: Incorpora programación defensiva para gestionar casos límite y entradas inesperadas.
Instalación y Primeros Pasos
Instalación del Módulo
npm install @suzilong/tree
# Alternativamente
yarn add @suzilong/tree
# O
pnpm install @suzilong/tree
Ejemplos de Uso Básico
import { traverseTree, findNode, convertArrayToTree } from '@suzilong/tree';
// Definición de una estructura de árbol simple
const estructuraPrincipal = {
identificador: 'RAIZ',
descendientes: [
{ identificador: 'A1', descendientes: [{ identificador: 'A1-1' }] },
{ identificador: 'A2' }
]
};
// Recorrido de los elementos del árbol
traverseTree(estructuraPrincipal, (elemento) => {
console.log(elemento.identificador);
});
// Salida esperada: RAIZ, A1, A1-1, A2 (por defecto, profundidad pre-orden)
// Búsqueda de un nodo específico
const nodoEncontrado = findNode(estructuraPrincipal, (elemento) => elemento.identificador === 'A2');
console.log(nodoEncontrado); // Salida: { identificador: 'A2' }
// Transformación de un listado plano a una estructura de árbol
const listaPlana = [
{ clave: '1', padreClave: null },
{ clave: '2', padreClave: '1' },
{ clave: '3', padreClave: '1' }
];
const nuevoArbolGenerado = convertArrayToTree(listaPlana, { idKey: 'clave', parentIdKey: 'padreClave' });
console.log(nuevoArbolGenerado);
// Salida: [{ clave: '1', children: [{ clave: '2' }, { clave: '3' }] }]
Funcionalidades Avanzadas Detalladas
1. Operaciones de Recorrido
forEach: Ejecuta una función en cada nodo del árbol.depthFirst: Recorrido en profundidad (configurable para pre-orden o post-orden).breadthFirst: Recorrido en anchura.
2. Operaciones de Búsqueda
find: Localiza el primer nodo que cumple una condición.findAll: Recpuera todos los nodos que satisfacen un predicado.findPath: Encuentra un nodo y devuelve el camino completo desde la raíz.
3. Operaciones de Modificación
appendChild: Agrega un nodo hijo al final de la lista de hijos de un padre.prependChild: Inserta un nodo hijo al principio de la lista de hijos de un padre.insertBefore: Inserta un nodo antes de un nodo de referencia específico.insertAfter: Inserta un nodo después de un nodo de referencia específico.remove: Elimina nodos que cumplen una condición.replace: Sustituye nodos existentes por otros nuevos.move: Reubica un nodo a una nueva posición dentro del árbol.
4. Operaciones de Conversión
arrayToTree: Convierte un arreglo plano de objetos en una estructura de árbol.treeToArray: Transforma una estructura de árbol de vuelta a un arreglo plano.map: Aplica una función de mapeo a cada nodo, generando un nuevo árbol.filter: Crea un nuevo árbol que contiene solo los nodos que cumplan una condición.reduce: Aplica una función acumuladora a los nodos del árbol.flat: Genera un arreglo unidimensional con todos los nodos del árbol.
5. Operaciones de Consulta
getCount: Retorna el número total de nodos.getLeafCount: Obtiene el número de nodos hoja.getDepth: Calcula la profundidad máxima del árbol.getAncestors: Recupera todos los ancestros de un nodo dado.getDescendants: Obtiene todos los descendientes de un nodo.getSiblings: Retorna los hermanos de un nodo específico.
Escenarios de Aplicación Práctica
Escenario 1: Construcción de Menús Jerárquicos
import { arrayToTree } from '@suzilong/tree';
// Datos de opciones de menú recibidos de un backend
const datosMenuNavegacion = [
{ item_id: '100', nombre: 'Inicio', padre_id: null },
{ item_id: '200', nombre: 'Gestión de Contenidos', padre_id: null },
{ item_id: '201', nombre: 'Artículos', padre_id: '200' },
{ item_id: '202', nombre: 'Categorías', padre_id: '200' }
];
// Conversión a una estructura de árbol para renderización
const menuArbolizado = arrayToTree(datosMenuNavegacion, { idKey: 'item_id', parentIdKey: 'padre_id' });
// 'menuArbolizado' está listo para construir un menú anidado en el frontend.
Escenario 2: Procesamiento de Estructuras Organizacionales
import { findNode, getDescendants } from '@suzilong/tree';
// Representación de un organigrama empresarial
const diagramaOrganizacional = {
area_id: 'CORPORACION',
titulo: 'Empresa Principal',
sub_areas: [
{
area_id: 'DEP_ING',
titulo: 'Departamento de Ingeniería',
sub_areas: [
{ area_id: 'EMP_1', titulo: 'Ana García' },
{ area_id: 'EMP_2', titulo: 'Carlos Ruiz' }
]
},
{
area_id: 'DEP_MKT',
titulo: 'Departamento de Marketing',
sub_areas: [
{ area_id: 'EMP_3', titulo: 'Elena Torres' }
]
}
]
};
// Localizar un empleado específico
const empleadoBuscado = findNode(diagramaOrganizacional, (nodo) => nodo.area_id === 'EMP_1');
console.log(empleadoBuscado.titulo); // Salida: Ana García
// Obtener todos los miembros del departamento de Ingeniería
const miembrosIngenieria = getDescendants(diagramaOrganizacional, (nodo) => nodo.area_id === 'DEP_ING', { childrenKey: 'sub_areas' });
console.log(miembrosIngenieria.length); // Salida: 2
Escenario 3: Filtrado de Datos en Categorías Jerárquicas
import { filterTree } from '@suzilong/tree';
// Árbol de clasificación de productos
const clasificacionProductos = {
categoria_id: 'GLOBAL',
etiqueta: 'Todos los Productos',
sub_categorias: [
{
categoria_id: 'ELECTRONICOS',
etiqueta: 'Dispositivos Electrónicos',
sub_categorias: [
{ categoria_id: 'TELEFONOS', etiqueta: 'Teléfonos Móviles' },
{ categoria_id: 'LAPTOPS', etiqueta: 'Ordenadores Portátiles' }
]
},
{
categoria_id: 'ROPA',
etiqueta: 'Indumentaria',
sub_categorias: [
{ categoria_id: 'CAMISAS', etiqueta: 'Camisas' },
{ categoria_id: 'PANTALONES', etiqueta: 'Pantalones' }
]
}
]
};
// Filtrar para mostrar solo la categoría de "Dispositivos Electrónicos" y sus hijos
const filtroElectronica = filterTree(
clasificacionProductos,
(nodo) => nodo.categoria_id === 'GLOBAL' || nodo.categoria_id === 'ELECTRONICOS' || ['TELEFONOS', 'LAPTOPS'].includes(nodo.categoria_id),
{ childrenKey: 'sub_categorias' }
);
console.log(filtroElectronica.sub_categorias.length); // Salida: 1
Ventajas de Usar @suzilong/tree
- Amplia Cobertura: Aborda casi todas las necesidades comunes de manipulación de árboles.
- Interfaz Intuitiva: Su API está diseñada para ser clara y fácil de aprender.
- Seguridad de Tipos: Al estar escrita en TypeScript, ofrece beneficios de verificación de tipos en tiempo de desarrollo.
- Eficiencia Optimizada: Rendimiento adecuado incluso con grandes volúmenes de datos.
- Mínima Huella: Sin dependencias, lo que contribuye a un tamaño de paquete pequeño.
- Soporte para Bosques: Flexibilidad para trabajar con múltiples estructuras de árbol a la vez.
- Modelo Inmutable: Evita efectos secundarios no deseados al no modificar las estructuras originales.
- Manejo de Casos Extremos: Diseñada para operar correctamente bajo diversas condiciones de entrada.
En resumen, @suzilong/tree se posiciona como una herramienta indispensable para cualquier desarrollador que trabaje con datos jerárquicos. Su enfoque en la exhaustividad funcional y la facilidad de uso la convierte en una opción sólida para optimizar los procesos de desarrollo.