10. Colecciones
Junto a las funciones, recorrer una lista de valores forma parte del flujo natural cuando se trabaja con la programación funcional. La gran mayoría de los problemas se pueden resolver iterando un conjunto de elementos con una intención: filtrar, modificar, calcular, etc. Por ello debes conocer las diferentes listas, o colecciones, para desenvolverte con comodidad.
Son las siguientes:
-
Vector: lista donde los nuevos elementos son añadidos al final.
-
List: lista donde los nuevos elementos son añadidos al principio. No puede usarse
(get), tan solo(nth). Recomendado para Macros. -
Map: colección Hashed, cada valor está acompañado por una llave (key).
-
Set: colección Hashed, donde no se permite la repetición.
No olvidemos el principio de la inmutabilidad. Cuando hago referencia a "añadir" a una lista, quiero expresar que crearé una nueva lista con un elemento extra a la original donde se situará al principio o el final. En otras palabras: una lista nueva con un elemento nuevo. Todo son constantes, no podemos modificarla.
Vector
Crear
Tienes dos posibilidades, usando la sintaxis de corchetes.
[1 2 3]
O usando la función vector.
(vector 1 2 3)
Leer
Para obtener un valor necesitarás usar get junto a la posición.
Partamos del siguiente vector.
["abc" false 99]
Si quiero obtener el primer elemento, o el string "abc".
(get ["abc" false 99] 0)
;;; "abc"
En cambio, si quiero quedarme con el valor de la tercera posición, o 99.
(get ["abc" false 99] 2)
;; 99
Puedes dar un valor por defecto si proporcionas una posición que se sale de rango o no existe.
(get ["abc" false 99] 6 "Vikingo")
;; Vikingo
Un atajo popularmente utilizado es usar -> o función de encadenamiento. Lo estudiaremos más adelante en el tema Threading Macros.
(-> 0 ["abc" false 99])
;; abc
Añadir
Usaremos conj, una función que te permite crear una nueva lista con nuevos elementos.
(conj [1 2 3] 4)
;; [1 2 3 4]
(conj [1 2 3] 4 5 6)
;; [1 2 3 4 5 6]
Al ser un Vector siempre se colocarán al final. En caso de que quieras unir 2 o más Vectores, usaremos into.
(into [1 2 3 4 5 6] ["a" "b"])
; [1 2 3 4 5 6 "a" "b"]
Si quieres añadir elementos delante no te quedará otra que transformarlo en una List. La función cons hace la conversión además de actuar como conj.
(cons 4 [1 2 3])
; (4 1 2 3)
Cuidado porque devolverá una colección de tipo List. Habrá que convertirlo a Vector con vec.
(vec (cons 4 [1 2 3]))
; [4 1 2 3]
Quitar
Para eliminar un valor es bastante natural usando filter.
De la colección ["a" "b" "c" "b"] quiero eliminar cualquier "b". De nuevo habría que convertirlo en Vector ya que devuelve un List.
(vec (filter (fn [value] (not= "b" value)) ["a" "b" "c" "b"]))
;; ["a" "c"]
Otra opción es usar subvec para cortar de un punto a otro. Por ejemplo, podríamos obtener un nuevo vector de la posición 1 en adelante.
(subvec ["a" "b" "c" "d"] 1)
;; ["b" "c" "d"]
O de la posición 1 a la 3.
(subvec ["a" "b" "c" "d"] 1 3)
;; ["b" "c"]
List
Crear
Puedes usar la sintaxis de paréntesis iniciando con una comilla simple.
'(1 2 3)
O la función list.
(list 1 2 3)
Leer
En lugar de get, disponemos de nth que es equivalente.
(nth '("PC" "MAC") 1)
;; "MAC"
Añadir o quitar
No hay diferencia. Podemos usar las mismas herramientas que los Vectores.
(conj '(1 2 3) 4)
;; (4 1 2 3)
(conj '(1 2 3) 4 5 6)
;; (6 5 4 1 2 3)
(filter (fn [value] (not= "b" value)) ["a" "b" "c" "b"])
;; ("a" "c")
Set
Crear
Como en los casos anteriores, puedes apoyarte en la sintaxis: llaves con una # al inicio.
#{"Alice" "Fred" "Bob" "Kelly"}
;; #{"Alice" "Fred" "Bob" "Kelly"}
O la función hash-set.
(hash-set "Alice" "Fred" "Bob" "Kelly")
;; #{"Alice" "Fred" "Bob" "Kelly"}
Es interesante utilizar la función ya que si incluyes un elemento duplicado, lo eliminará de forma automática. Mientras que si usas la sintaxis devolverá un error.
Quitar
Para eliminar disponemos de disj.
Puede usarse con un solo elemento.
(disj #{"Alice" "Fred" "Bob" "Kelly"} "Bob")
;; #{"Alice" "Kelly" "Fred"}
O varios, aunque alguno de ellos no exista.
(disj #{"Alice" "Fred" "Bob" "Kelly"} "Bob" "Batman" "Fred")
;; #{"Alice" "Kelly"}
Algunas ayudas
Comprobar si existe
Una característica interesante es que podemos preguntar si existe algún elemento con contains?.
(contains? #{"Alice" "Fred" "Bob" "Kelly"} "Bob")
;; true
(contains? #{"Alice" "Fred" "Bob" "Kelly"} "Truman")
;; false
Ordenar
Utilizando sorted-set y conj podemos ordenar un contenido de una manera rápida.
(conj (sorted-set) "Bravo" "Charlie" "Sigma" "Alpha")
;; #{"Alpha" "Bravo" "Charlie" "Sigma"}
Maps
Crear
Si usamos la sintaxis, nos recordará a un JSON con algunas diferencias.
Envolviendo en llaves, las posiciones impares son las claves y las pares los valores.
{"Fred" 1400 "Bob" 1240 "Angela" 1024}
;; {"Fred" 1400, "Bob" 1240, "Angela" 1024}
Si te resulta más cómodo puedes usar comas, aunque es opcional.
{"Fred" 1400, "Bob" 1240, "Angela" 1024}
;; {"Fred" 1400, "Bob" 1240, "Angela" 1024}
O saltos de línea.
{
"Fred" 1400
"Bob" 1240
"Angela" 1024
}
;; {"Fred" 1400, "Bob" 1240, "Angela" 1024}
Por otro lado pueden ser útiles para representar datos estructurados, o cuando necesitas reutilizar un formato de mapa.
Las claves se acompañan de : al inicio. Este tipo de claves se denominan keywords y son el estilo idiomático en Clojure.
(def person
{:first-name "James"
:last-name "Bond"
:age 32
:occupation "Agente secreto"})
Leer
Para obtener un valor usamos get con la clave, o directamente la keyword como función.
(get person :occupation)
;; "Agente secreto"
(:occupation person)
;; "Agente secreto"
Podemos dar un valor por defecto en caso de no existir la clave.
(:favorite-color person "beige")
;; "beige"
Añadir
(assoc {"Fred" 1400, "Bob" 1240, "Angela" 1024} "Sally" 0)
;; {"Fred" 1400, "Bob" 1240, "Angela" 1024, "Sally" 0}
Modificar
(assoc {"Fred" 1400, "Bob" 1240, "Angela" 1024} "Fred" 100)
;; {"Fred" 100, "Bob" 1240, "Angela" 1024}
Borrar
(dissoc {"Fred" 1400, "Bob" 1240, "Angela" 1024} "Bob")
;; {"Fred" 1400, "Angela" 1024}
Ayudas comunes
Conocer la longitud
(count ["a" "b" "c"])
;; 3
Obtener primer elemento
(first ["a" "b" "c"])
;; "a"
Obtener último elemento
(last ["a" "b" "c"])
;; "c"
Varios niveles (Anidación)
Crear
(def company
{:name "WidgetCo"
:address {:street "123 Main St"
:city "Springfield"
:state "IL"}})
Leer
(get-in company [:address :city])
;; "Springfield"
Actualizar
(assoc-in company [:address :street] "303 Broadway")
Records
Para el caso anterior tiene un mejor desempeño usar Records en lugar de Maps.
Definir.
(defrecord Person [first-name last-name age occupation])
Generar.
(def kelly (->Person "Kelly" "Keen" 32 "Programmer"))
Generar indicando las claves.
(def kelly (map->Person
{:first-name "Kelly"
:last-name "Keen"
:age 32
:occupation "Programmer"}))
Resumen
-
Vector (
[]): lista ordenada, añade al final. Usagetpara leer. -
List (
’()): lista ordenada, añade al principio. Usanthpara leer. -
Set (
#{}): colección sin duplicados. Usacontains?para comprobar. -
Map (
{}): pares clave-valor. Usageto la keyword como función. -
conjañade elementos,assocen Maps. -
filterydisjpara eliminar elementos. -
get-inyassoc-inpara trabajar con estructuras anidadas. -
defrecordpara estructuras con mejor rendimiento.
Ejercicios
-
Crea un vector con los nombres de 5 personajes del Quijote. Obtén el tercero.
-
Crea un Map con las claves
:nombre,:edady:profesion. Añade una nueva clave:ciudadcon el valor"LaMancha". -
Dado el Set
#{"espada""lanza""escudo""yelmo"}, comprueba si existe"lanza"y elimina"escudo". -
Crea una estructura anidada que represente un caballero con su nombre y su caballo (nombre y raza). Lee el nombre del caballo.
This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.
Support me on Ko-fi