Cómo Trabajar con Mapas en Groovy
En este artículo, aprenderemos cómo trabajar con mapas en Groovy, un poderoso lenguaje que se ejecuta sobre la plataforma Java y que amplía la API de Map de Java. Esto proporciona métodos para operaciones tales como filtrado, búsqueda y ordenamiento, además de ofrecer una variedad de formas abreviadas para crear y manipular mapas. A lo largo de este tutorial, exploraremos la forma “Groovy” de trabajar con mapas y cómo esto puede mejorar drásticamente nuestra eficiencia como programadores.
1. Overview
Groovy extiende la API de Map en Java para proporcionar métodos para operaciones tales como filtrado, búsqueda y ordenamiento. También ofrece una variedad de formas abreviadas para crear y manipular mapas. En este tutorial, analizaremos cómo trabajar con mapas en Groovy.
2. Creando Mapas en Groovy
Podemos usar la sintaxis de literal de mapa [k:v] para crear mapas. Esto permite instanciar un mapa y definir entradas en una sola línea.
Para crear un mapa vacío, se puede usar:
def emptyMap = [:]
De igual forma, un mapa con valores se puede instanciar así:
def map = [name: "Jerry", age: 42, city: "New York"]
Es importante notar que las claves no están rodeadas por comillas, y por defecto, Groovy crea una instancia de java.util.LinkedHashMap. Podemos cambiar este comportamiento predeterminado usando el operador as.
3. Agregando Elementos a un Mapa
Comencemos definiendo un mapa:
def map = [name:"Jerry"]
Podemos agregar una clave al mapa:
map["age"] = 42
Sin embargo, también existe una forma más parecida a JavaScript usando la notación de propiedades (el operador punto):
map.city = "New York"
De esta manera, Groovy permite acceder a las parejas clave-valor de una manera similar a un bean.
También podemos usar variables en lugar de literales como claves al agregar nuevos elementos al mapa:
def hobbyLiteral = "hobby"
def hobbyMap = [(hobbyLiteral): "Singing"]
map.putAll(hobbyMap)
assertTrue(hobbyMap.hobby == "Singing")
assertTrue(hobbyMap[hobbyLiteral] == "Singing")
Primero, creamos una nueva variable que almacena la clave hobby. Luego utilizamos esta variable entre paréntesis con la sintaxis de literal del mapa para crear otro mapa.
4. Recuperando Elementos de un Mapa
Se puede utilizar la sintaxis literal o la notación de propiedades para obtener elementos de un mapa. Para un mapa declarado como:
def map = [name:"Jerry", age: 42, city: "New York", hobby:"Singing"]
Podemos obtener el valor correspondiente a la clave name:
assertTrue(map["name"] == "Jerry")
o
assertTrue(map.name == "Jerry")
5. Eliminando Elementos de un Mapa
Podemos eliminar cualquier entrada de un mapa basado en una clave usando el método remove(), pero a veces es necesario eliminar múltiples entradas de un mapa. Esto lo podemos hacer usando el método minus().
El método minus() acepta un Mapa y devuelve un nuevo Mapa después de eliminar todas las entradas del mapa dado del mapa subyacente:
def map = [1:20, a:30, 2:42, 4:34, ba:67, 6:39, 7:49]
def minusMap = map.minus([2:42, 4:34]);
assertTrue(minusMap == [1:20, a:30, ba:67, 6:39, 7:49])
Luego, también podemos eliminar entradas basadas en una condición usando el método removeAll():
minusMap.removeAll{it -> it.key instanceof String}
assertTrue(minusMap == [1:20, 6:39, 7:49])
Por el contrario, para retener todas las entradas que satisfacen una condición, podemos usar el método retainAll():
minusMap.retainAll{it -> it.value % 2 == 0}
assertTrue(minusMap == [1:20])
6. Iterando a Través de Entradas
Podemos iterar a través de las entradas usando los métodos each() y eachWithIndex(). Estos métodos permiten iterar sobre las entradas de un mapa aprovechando las closures de Groovy que proporcionan una sintaxis concisa.
A continuación, un ejemplo donde iteramos a través de cada Entry:
map.each{entry -> println "$entry.key: $entry.value"}
Ahora, usaremos el método eachWithIndex() para imprimir el índice actual junto con los demás valores:
map.eachWithIndex{entry, i -> println "$i $entry.key: $entry.value"}
También es posible pedir que las key, value y el índice se suministren por separado:
map.eachWithIndex{key, value, i -> println "$i $key: $value"}
7. Filtrando
Podemos usar los métodos find(), findAll(), y grep() para filtrar y buscar entradas en el mapa basadas en claves y valores.
Empecemos definiendo un mapa para ejecutar estos métodos:
def map = [name:"Jerry", age: 42, city: "New York", hobby:"Singing"]
Comencemos con el método find(), que acepta una Closure y devuelve la primera Entry que coincide con la condición de la Closure:
assertTrue(map.find{it.value == "New York"}.key == "city")
De manera similar, findAll también acepta una Closure, pero devuelve un Map con todas las parejas clave-valor que satisfacen la condición de la Closure:
assertTrue(map.findAll{it.value == "New York"} == [city : "New York"])
Si preferimos usar una List, podemos usar grep en lugar de findAll:
map.grep{it.value == "New York"}.each{it -> assertTrue(it.key == "city" && it.value == "New York")}
8. Transformando y Coleccionando
En ocasiones, es posible que queramos transformar las entradas en un mapa en nuevos valores. Usando los métodos collect() y collectEntries(), es posible transformar y coleccionar entradas en una Collection o Map, respectivamente.
Veamos algunos ejemplos. Dado un mapa de ID de empleados y empleados:
def map = [
1: [name:"Jerry", age: 42, city: "New York"],
2: [name:"Long", age: 25, city: "New York"],
3: [name:"Dustin", age: 29, city: "New York"],
4: [name:"Dustin", age: 34, city: "New York"]
]
Podemos coleccionar los nombres de todos los empleados en una lista usando collect():
def names = map.collect{entry -> entry.value.name}
assertTrue(names == ["Jerry", "Long", "Dustin", "Dustin"])
Para obtener un conjunto único de nombres, podemos especificar la colección pasando un objeto Collection:
def uniqueNames = map.collect([] as HashSet){entry -> entry.value.name}
assertTrue(uniqueNames == ["Jerry", "Long", "Dustin"] as Set)
Si queremos cambiar los nombres de los empleados en el mapa de minúsculas a mayúsculas, podemos usar collectEntries. Este método devuelve un mapa de valores transformados:
def idNames = map.collectEntries{key, value -> [key, value.name]}
assertTrue(idNames == [1:"Jerry", 2:"Long", 3:"Dustin", 4:"Dustin"])
Y también es posible usar los métodos collect en conjunto con find y findAll para transformar los resultados filtrados:
def below30Names = map.findAll{it.value.age < 30}.collect{key, value -> value.name}
assertTrue(below30Names == ["Long", "Dustin"])
9. Agrupando
A veces podemos querer agrupar algunos elementos de un mapa en submapas basados en una condición.
El método groupBy() devuelve un mapa de mapas, y cada mapa contiene pares clave-valor que evalúan de la misma manera para la condición dada:
def map = [1:20, 2: 40, 3: 11, 4: 93]
def subMap = map.groupBy{it.value % 2}
assertTrue(subMap == [0:[1:20, 2:40], 1:[3:11, 4:93]])
Otra forma de crear submapas es usando subMap(). Se diferencia de groupBy() en que solo permite agrupar basados en las claves:
def keySubMap = map.subMap([1,2])
assertTrue(keySubMap == [1:20, 2:40])
En este caso, las entradas para las claves 1 y 2 se devuelven en el nuevo mapa, y todas las demás entradas se descartan.
10. Ordenando
Normalmente cuando ordenamos, puede que queramos ordenar las entradas en un mapa basadas en clave o valor o ambos. Groovy proporciona un método sort() que se puede utilizar para este propósito.
Dado un mapa:
def map = [ab:20, a: 40, cb: 11, ba: 93]
Si se requiere ordenar por clave, usaremos el método sort() sin argumentos, que se basa en el orden natural:
def naturallyOrderedMap = map.sort()
assertTrue([a:40, ab:20, ba:93, cb:11] == naturallyOrderedMap)
O podemos usar sort(Comparator) para proporcionar lógica de comparación:
def compSortedMap = map.sort({k1, k2 -> k1 <=> k2} as Comparator)
assertTrue([a:40, ab:20, ba:93, cb:11] == compSortedMap)
A continuación, para ordenar bien ya sea por clave, valores, o ambos, podemos proporcionar una Closure a sort():
def cloSortedMap = map.sort({it1, it2 -> it1.value <=> it2.value})
assertTrue([cb:11, ab:20, a:40, ba:93] == cloSortedMap)
11. Conclusión
En este artículo, aprendimos cómo crear Maps en Groovy. Luego analizamos diferentes maneras de agregar, recuperar y eliminar elementos de un mapa. Finalmente, cubrimos los métodos que Groovy proporciona para realizar operaciones comunes, como filtrado, búsqueda, transformación y ordenación.
Esperamos que este tutorial le haya proporcionado una comprensión sólida de cómo trabajar con mapas en Groovy y que pueda aplicar estos métodos en sus propios proyectos de programación, mejorando así la calidad y eficiencia de su código.