Programación Recursiva: Ejemplo y desventajas
Quiero hablar un poco sobre un tema que considero muy interesante: la recursividad, o programación recursiva. En pocas palabras, puede que no la conozcas, ya que es un tema que realmente no veo que se mencione tanto hoy en día. Es algo que traje desde la universidad, y desde entonces, en todos los cursos y libros que he leído, realmente nunca lo he vuelto a ver mencionado.
Por eso me pareció interesante traerlo aquí, ya que puede ser muy útil en determinadas situaciones. Si ya la conoces, sabrás que tiene sus ventajas y desventajas, pero como se dice, hay que saber implementarla bien. Así que quiero mostrarte un ejemplo práctico de cómo la estoy usando.
El caso práctico: HTML a widgets nativos
Como te comenté anteriormente, hice una pequeña implementación que parte de un archivo HTML. Todo esto que ves es una página HTML tal cual, con sus imágenes, div, listas (ul, ol), párrafos, etc. Lo interesante que quise hacer fue traducir ese HTML (que originalmente estaba en un visor web) a widgets nativos. Esto, por supuesto, trae muchas ventajas, aunque no voy a profundizar en eso ahora.
Lo importante aquí es que la recursividad me sirvió mucho. Ya irás entendiendo por qué.
El problema de los div anidados
Tengo una función llamada htmlToWidget, que es la encargada de procesar el documento HTML. Básicamente, una vez que obtengo el documento, empiezo a iterarlo y verifico qué tipo de nodo es: si es un H1, un H2, un p, una img, un ul, etc., y en función de eso, genero el widget correspondiente.
Todo funciona bien... hasta que aparecen los div.
El problema es que un div puede contener más elementos HTML dentro. Y si lo hacemos de manera tradicional, solo se buscan los hijos directos. Pero muchos de esos elementos pueden estar más profundamente anidados. Por ejemplo, en estructuras como esta:
<div>
<div>
<section>
<p>Texto</p>
</section>
</div>
</div>Si solo iteramos los hijos directos, vamos a perder gran parte del contenido, ya que no es accesible en el primer nivel de iteración.
Solución con recursividad
Cuando me encuentro con un div o un section, llamo a una función llamada share que, a su vez, llama a processHTML, que es recursiva. Es decir, si encuentro un div, paso sus hijos a esa función, y así sucesivamente.
switch (element.localName) {
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
Text(...)
break;
case 'ul':
case 'ol':
Text(...)
break;
case 'p':
Text(...)
break;
case 'img':
return [processImage(element)];
case 'div':
case 'section':
// buscamos contenido interno
return _searchContent(element, context);
}
List<Widget> _searchContent(html_dom.Element element, BuildContext context) {
// Función auxiliar para buscar contenido dentro de un section
if (element.children.isEmpty) return [];
List<Widget> ws = [];
element.children.forEach((e) {
_processHTML(e, context).forEach((w) {
ws.add(w);
});
});
return ws;
}
La función procesará al primer div, luego al hijo, luego al h2, luego al p, y así hasta llegar al contenido que quiero mostrar en el widget.
Aquí es donde la recursividad brilla. Si no la usara, tendría que hacer múltiples forEach anidados o copiar y pegar lógica en distintos bloques. Sería un lío manejar cada nivel de anidamiento de manera manual.
Desventajas de la recursividad
Antes de terminar, quiero hablar brevemente de sus desventajas, porque hay que usarla con conciencia:
- Mayor consumo de memoria: debido a que cada llamada recursiva queda en la pila.
- Desbordamiento de pila: si no se controla bien, puede generar errores.
- Difícil de depurar: seguir el rastro de las llamadas puede ser más complejo que con bucles tradicionales.
- Menor eficiencia: puede ser menos performante que un forEach plano.
Por eso entiendo que haya sido un concepto un poco abandonado. Pero, como te muestro aquí, hay casos en los que es la herramienta correcta.
Acepto recibir anuncios de interes sobre este Blog.
La recursividad es una maravilla, te muestro un ejemplo de la implementación de html to widgets.