Estructuras de Datos

  • Vectores
  • Mapas

Grupo de datos - Colecciones

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.

Vectores

Colección secuencial

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.

Estructura como compartimento

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.

Vector

Sintaxis

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]
[]

Ejemplo

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]

Creación

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 nombre conj? conj es abreviatura de conjoin, 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]

Extracción

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, llamar nth 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 y nth 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)

EJERCICIO 1: Ver los nombres de las tortugas

  1. Agregá una tortuga con una pieza de código en el archivo
    • Abrí el archivo walk.clj
    • Agregá una línea: (add-turtle :neo) en la última línea del archivo
    • Seleccioná esta línea y clickeá “Reload Selection”
  2. (Opcional) agregá una tortuga usando el REPL
    • Tipeá (add-turtle :oracle) seguido de enter en el panel del REPL de abajo
  3. Ver los nombres de las tortugas
    • Tipeá (turtle-names) en el panel del REPL de abajo y mirá el resultado

EJERCICIO 2: Crear un vector

  • Ve a core.clj en myproject y prendé el InstaREPL
  • Creá un vector de las temperaturas máximas de los próximos 7 días de la ciudad donde vivís
  • Luego usá la función nth para obtener la temperatura máxima del próximo Martes

Mapas

Pares clave valor

Los 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.

Mapa

Sintaxis

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"}
{}

Ejemplo

Cuando una tortuga recibe un comando como forward o right, devuelve el resultado como un mapa con un mapa adentro.

(forward 40)
;=> {:trinity {:length 40}}
(right 90)
;=> {:trinity {:angle 90}}

Creación

assoc y dissoc hacen pareja: asocian y desasocian elementos de un mapa. Mirá como agregamos el apellido “Brown” al mapa con assoc, y después lo removemos con dissoc. 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"}

Extracción 1

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

Extracción 2

Luego tenemos keys y vals, 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")

Actualizar

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ón update. 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ón update-in funciona como update, 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"}}

Colecciones de Colecciones

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.

Vectores de Mapas

(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]}}

Mapa de Mapas

(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

EJERCICIO 3: Ver los estados de las tortugas

  • Abrí el archivo walk.clj
  • Probá los ejemplos de las dos previas diapositivas en el REPL
  • Notá que valores obtenés

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])

EJERCICIO 4: Modelarse a uno mismo

  • Ve a core.clj en myproject y prendé el InstaREPL
  • Hacé un mapa que te represente a vos mismo
  • Asegurate de que contenga tu nombre y apellido
  • Luego, agregá tu ciudad natal al mapa usando assoc o merge

EJERCICIO 5 [BONUS]: Modelar a tus compañeros

  • Primero, tomá el mapa que hiciste de vos mismo en el ejercicio anterior
  • Luego, creá un vector de mapas que contenga el nombre, apellido y ciudad natal de dos o tres compañeros cerca tuyo
  • Finalmente, añadí tu mapa a esta información usando conj

Volvé al primer slide, o andá al outline del curriculum.