Hasta ahora, vimos piezas discretas de datos: un número, una string, un valor. Cuando programamos, es más frecuente que queramos trabajar con grupos de datos.
Clojure tiene excelentes herramientas para trabajar con estos grupos, o colecciones, de datos. No solo nos proveé con cuatro tipos distintos de colecciones, sino también de una manera uniforme de usarlas.
Un vector es una colección secuencial de valores. Un vector puede estar vacío. Un vector puede contener valores de tipos distintos. Cada valor en un vector está numerado empezando en 0, a ese número se lo llama su índice. El índice se usa para referenciar a cada valor cuando se lo quiere buscar.
Para imaginarte un vector, imaginá una caja dividida en algún número de compartimentos del mismo tamaño. Cada compartimento tiene un número. Podes poner un dato adentro de cada compartimento y siempre saber donde encontrarlo, ya que tiene un número.
Notá que los números empiezan en el 0. Esto puede resultar extraño, pero es común contar desde 0 cuando programamos.
Los vectores se escriben usando corchetes, con cualquier cantidad de datos adentro, separados por espacios. Estos son algunos ejemplos de vectores:
[1 2 3 4 5]
[56.9 60.2 61.8 63.1 54.3 66.4 66.5 68.1 70.2 69.2 63.1 57.1]
[]
Cuando hay una pareja de tortugas, el comando
(turtle-names)
devuelve los nombres de las tortugas como un vector:
(turtle-names)
;=> [:trinity :neo :oracle :cypher]
Las siguientes dos funciones se usan para crear vectores. La función
vector
toma cualquier cantidad de elementos y los pone en un nuevo vector.conj
es una función interesante que verás siendo usada con todas las estructuras de datos. Con los vectores, toma un vector y un elemento, y devuelve un nuevo vector con ese elemento agregado al final del vector original. ¿Por qué el nombreconj
?conj
es abreviatura deconjoin
, que significa unir o combinar. Eso es lo que hacemos: estamos uniendo el nuevo elemento al vector.
(vector 5 10 15)
;=> [5 10 15]
(conj [5 10] 15)
;=> [5 10 15]
Ahora, mira estas cuatro funciones.
count
nos dice cuantos elementos hay en un vector.nth
nos da el enésimo elemento del vector. Notá que empezamos contando en 0, por lo que en el ejemplo, llamarnth
con el número 1 nos devuelve lo que llamaríamos el segundo elemento cuando no estamos programando.first
devuelve el primer elemento en la colección.rest
devuelve todos excepto el primero. Tratá de no pensar en eso ynth
al mismo tiempo, porque puede ser confuso.
(count [5 10 15])
;=> 3
(nth [5 10 15] 1)
;=> 10
(first [5 10 15])
;=> 5
(rest [5 10 15])
;=> (10 15)
walk.clj
(add-turtle :neo)
en la última línea del archivo(add-turtle :oracle)
seguido de enter en el panel del REPL de abajo(turtle-names)
en el panel del REPL de abajo y mirá el resultadocore.clj
en myproject
y prendé el InstaREPLnth
para obtener la temperatura máxima del próximo
MartesLos mapas contienen un conjunto de claves y valores asociados a estas. Podés pensarlo como un diccionario: buscas cosas usando una palabra (la palabra clave) y ves la definición (el valor). Si programaste en otro lenguaje, habrás visto algo como los mapas–quizás llamados diccionarios, hashes, o arrays asociativos.
Escribimos los mapas encerrando claves y valores alternados entre llaves, como se puede ver abajo.
Los mapas son útiles porque contienen datos de la forma que normalmente lo pensamos. Tomá nuestro ejemplo inventado, Sally Brown. Un mapa puede contener su nombre y su apellido, su dirección, su comida favorita, o cualquier otra cosa. Es una forma sencilla de juntar esos datos y hacerlos fácil de buscar. El último ejemplo es un mapa vacío. Es un mapa listo para contener cosas, pero que no tiene nada todavía.
{:first "Sally" :last "Brown"}
{:a 1 :b "two"}
{}
Cuando una tortuga recibe un comando como
forward
oright
, devuelve el resultado como un mapa con un mapa adentro.
(forward 40)
;=> {:trinity {:length 40}}
(right 90)
;=> {:trinity {:angle 90}}
assoc
ydissoc
hacen pareja: asocian y desasocian elementos de un mapa. Mirá como agregamos el apellido “Brown” al mapa conassoc
, y después lo removemos condissoc
.merge
junta dos mapas y crea uno nuevo.
(assoc {:first "Sally"} :last "Brown")
;=> {:first "Sally", :last "Brown"}
(dissoc {:first "Sally" :last "Brown"} :last)
;=> {:first "Sally"}
(merge {:first "Sally"} {:last "Brown"})
;=> {:first "Sally", :last "Brown"}
count
, toda colección tiene esta función. ¿Por qué creés que la respuesta es dos?count
devuelve la cantidad de asociaciones.
Ya que un mapa es un par de claves y valores, la clave se usa para obtener un valor de un mapa. Una de las formas usuales en Clojure es como en el ejemplo debajo. Podemos usar una clave como una función para buscar valores en un mapa. En el último ejemplo, pusimos la clave
:MISS
. Esto funciona para cuando la clave que buscamos no está en el mapa.
(count {:first "Sally" :last "Brown"})
;=> 2
(get {:first "Sally" :last "Brown"} :first)
;=> "Sally"
(get {:first "Sally"} :last)
;=> nil
(get {:first "Sally"} :last :MISS)
;=> :MISS
Luego tenemos
keys
yvals
, que son bastante sencillas: devuelven las claves y los valores en un mapa. El orden no está garantizado, por lo que podríamos haber obtenido(:first :last)
o(:last :first)
.
(keys {:first "Sally" :last "Brown"})
;=> (:first :last)
(vals {:first "Sally" :last "Brown"})
;=> ("Sally" "Brown")
Luego de crear un mapa, queremos asignar un nuevo valor a una clave existente. La función
assoc
se puede usar para esto. También, existe la práctica funciónupdate
. Esta toma un mapa, una clave y una función. El valor de la clave será el primer argumento de la función dada. La funciónupdate-in
funciona comoupdate
, pero toma un vector de claves como camino en un mapa anidado.
(def hello {:count 1 :words "hello"})
(update hello :count inc)
;=> {:count 2, :words "hello"}
(update hello :words str ", world")
;=> {:count 1, :words "hello, world"}
(def mine {:pet {:age 5 :name "able"}})
(update-in mine [:pet :age] - 3)
;=> {:pet {:age 2, :name "able"}}
Valores simples como números, claves y strings no son los únicos tipos de cosas que podes poner en las colecciones, así que podes tener un vector de mapas, o una lista de vectores, o cualquiera sea la combinación que se ajuste a tus datos.
(state-all)
;=> [{:trinity {:x -1.7484556000744965E-6, :y 39.99999999999996, :angle 90, :color [106 40 126]}}
{:neo {:x 21.213202971967114, :y 21.213203899225725, :angle 45, :color [0 64 0]}}
{:oracle {:x -49.99999999999981, :y -4.3711390001862375E-6, :angle 180, :color [43 101 236]}}]
(def states (state-all))
;=> #'clojurebridge-turtle.walk/states
(first states)
;=> {:trinity {:x -1.7484556000744965E-6, :y 39.99999999999996,
:angle 90, :color [106 40 126]}}
(def st (first states))
;=> #'clojurebridge-turtle.walk/st
st
;=> {:trinity {:x -1.7484556000744965E-6, :y 39.99999999999996,
;=> :angle 90, :color [30 30 30]}}
(get st :trinity)
;=> {:x -1.7484556000744965E-6, :y 39.99999999999996,
;=> :angle 90, :color [30 30 30]}
(get-in st [:trinity :angle])
;=> 90
walk.clj
No te olvides de apretar enter cuando escribís código en el REPL
(state-all)
(def states (state-all))
(first states)
(def st (first states))
st
(get st :trinity)
(get-in st [:trinity :angle])
Volvé al primer slide, o andá al outline del curriculum.