Trabajar con listas en Racket es fundamental, ya que este lenguaje, basado en Lisp, está diseñado para procesar listas de manera eficiente y elegante. Empezamos definiendo algunas listas básicas para experimentar, como una lista con los números del 1 al 11, otra con los números del 1 al 3, y una tercera con los números del 4 al 6. Estas listas nos servirán para explorar las distintas funciones y técnicas que Racket nos ofrece para manipularlas.
Para acceder a los elementos de una lista, podemos usar funciones clásicas como car y cdr, que nos permiten obtener el primer elemento y el resto de la lista, respectivamente. Sin embargo, para facilitar la lectura y escritura del código, Racket también proporciona funciones más intuitivas como first para el primer elemento y rest para el resto. Además, existen funciones específicas para acceder a posiciones concretas, como second, third, fourth y hasta tenth, que nos devuelven el elemento en esa posición. Si queremos obtener el último elemento, podemos usar la función last.
Cuando necesitamos acceder a elementos más profundos sin escribir múltiples car y cdr, podemos usar combinaciones abreviadas como cadr (que equivale a car de cdr), caddr (que es car de cdr de cdr), o cddr para obtener el resto después de dos elementos. Estas funciones simplifican mucho el código y evitan la complejidad visual de múltiples paréntesis anidados.
Para acceder a cualquier elemento de la lista de forma arbitraria, Racket ofrece la función list-ref, que toma una lista y un índice (empezando en 0) y devuelve el elemento en esa posición. Por ejemplo, list-ref lista 0 devuelve el primer elemento, y así sucesivamente. Para conocer la cantidad de elementos que tiene una lista, usamos la función length.
Un aspecto muy común al trabajar con listas es combinarlas. La función append nos permite concatenar dos listas en una sola, manteniendo el orden de los elementos. Por ejemplo, unir la lista [1 2 3] con [4 5 6] nos da [1 2 3 4 5 6]. También podemos invertir una lista con la función reverse, que coloca el último elemento en primer lugar y así sucesivamente. Esto puede ser útil, por ejemplo, para obtener el último elemento de una lista haciendo car de la lista invertida.
Para comprobar si un elemento está presente en una lista, usamos la función member. Esta función devuelve la sublista que comienza con el elemento buscado si lo encuentra, o #f si no está presente. Para convertir esta respuesta en un valor booleano simple, podemos combinar member con la función not para obtener true si el elemento está en la lista y false si no.
Más allá de acceder y modificar listas, Racket nos permite trabajar con ellas de forma iterativa usando funciones de orden superior. La función map es una de las más importantes: toma una función y una o varias listas, y devuelve una nueva lista con el resultado de aplicar la función a cada elemento correspondiente. Por ejemplo, si aplicamos la función add1 (que suma 1 a un número) a la lista [1 2 3], obtenemos [2 3 4]. Si la función acepta varios parámetros, podemos pasar varias listas y map aplicará la función a los elementos correspondientes de cada lista.
Además de map, existen funciones booleanas como andmap y ormap. Ambas aplican una función que devuelve un valor booleano a cada elemento de una lista. andmap devuelve true solo si la función devuelve true para todos los elementos, mientras que ormap devuelve true si la función devuelve true para al menos un elemento. Por ejemplo, si definimos una función menor4 que verifica si un número es menor que 4, andmap menor4 lista devolverá true solo si todos los elementos son menores que 4, y ormap menor4 lista devolverá true si al menos uno cumple esa condición.
Otra función útil es filter, que nos permite obtener una nueva lista con solo los elementos que cumplen una condición booleana. Por ejemplo, filter menor4 lista devolverá una lista con todos los elementos menores que 4, descartando el resto.
Finalmente, la recursión es una técnica esencial para procesar listas en Racket. Podemos definir funciones recursivas que operan sobre listas verificando un caso base y luego llamándose a sí mismas con una versión reducida de la lista. Por ejemplo, para sumar todos los elementos de una lista, podemos definir una función sumar-lista que devuelve 0 si la lista está vacía (caso base), y en caso contrario suma el primer elemento (car) con el resultado de llamar a sumar-lista con el resto de la lista (cdr). Para comprobar si una lista está vacía, usamos la función empty.
Este enfoque recursivo es muy natural en Racket y otros lenguajes funcionales, y nos permite procesar listas de manera elegante y clara, aunque al principio pueda parecer complejo. Con práctica, entenderemos que la recursión es simplemente una forma de repetir operaciones hasta alcanzar una condición que detenga el proceso.
(define (sumar-lista l)
(if (empty? l)
0
(+ (car l) (sumar-lista (cdr l)))))
Con esta función, si le pasamos una lista vacía, devuelve 0. Si le pasamos una lista con elementos, suma el primero con la suma del resto, recorriendo toda la lista hasta completarla. Así, por ejemplo, sumar-lista '(1 2 3 4 5) devolverá 15.
En resumen, Racket nos ofrece un conjunto muy completo de herramientas para manipular listas, desde funciones básicas para acceder y modificar elementos, hasta funciones de orden superior para aplicar operaciones de forma iterativa, y técnicas recursivas para procesar listas de manera elegante y funcional. Estas habilidades son esenciales para aprovechar al máximo este lenguaje y entender la programación funcional en general.