«1. Обзор

Groovy расширяет Map API на Java, предоставляя методы для таких операций, как фильтрация, поиск и сортировка. Он также предоставляет множество сокращенных способов создания карт и управления ими.

В этой статье мы рассмотрим способ работы с картами в Groovy.

2. Создание карт Groovy

Мы можем использовать синтаксис литерала карты [k:v] для создания карт. По сути, это позволяет нам создавать экземпляр карты и определять записи в одной строке.

Пустая карта может быть создана с помощью:

def emptyMap = [:]

Точно так же карта со значениями может быть создана с помощью:

def map = [name: "Jerry", age: 42, city: "New York"]

Обратите внимание, что ключи не заключены в кавычки.

По умолчанию Groovy создает экземпляр java.util.LinkedHashMap. Мы можем переопределить это поведение по умолчанию, используя оператор as.

3. Добавление элементов

Давайте начнем с определения карты:

def map = [name:"Jerry"]

Мы можем добавить к карте ключ:

map["age"] = 42

Но еще один способ, более похожий на Javascript, — это использование нотации свойств ( оператор точки):

map.city = "New York"

Другими словами, Groovy поддерживает доступ к парам ключ-значение в виде bean-компонента.

Мы также можем использовать переменные вместо литералов в качестве ключей при добавлении новых элементов на карту:

def hobbyLiteral = "hobby"
def hobbyMap = [(hobbyLiteral): "Singing"]
map.putAll(hobbyMap)
assertTrue(hobbyMap.hobby == "Singing")
assertTrue(hobbyMap[hobbyLiteral] == "Singing")

Во-первых, мы должны создать новую переменную, в которой будет храниться ключевое хобби. Затем мы используем эту переменную, заключенную в круглые скобки, с синтаксисом литерала карты, чтобы создать другую карту.

4. Извлечение элементов

Литеральный синтаксис или нотация свойств могут использоваться для получения элементов из карты.

Для карты, определенной как:

def map = [name:"Jerry", age: 42, city: "New York", hobby:"Singing"]

Мы можем получить значение, соответствующее имени ключа:

assertTrue(map["name"] == "Jerry")

или

assertTrue(map.name == "Jerry")

5. Удаление элементов

Мы можем удалить любой запись с карты на основе ключа с помощью метода remove(). Но иногда нам может понадобиться удалить несколько записей с карты. Это можно сделать с помощью метода minus().

Метод minus() принимает карту. И возвращает новую карту после удаления всех записей данной карты из базовой карты:

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

Далее мы также можем удалить записи на основе условия. Этого можно добиться с помощью метода removeAll():

minusMap.removeAll{it -> it.key instanceof String}
assertTrue(minusMap == [1:20, 6:39, 7:49])

И наоборот, чтобы сохранить все записи, удовлетворяющие условию, мы можем использовать метод continueAll():

minusMap.retainAll{it -> it.value % 2 == 0}
assertTrue(minusMap == [1:20])

6. Итерация по записям ~~ ~ Мы можем перебирать записи, используя методы each() и eachWithIndex().

Метод each() предоставляет неявные параметры, такие как запись, ключ и значение, которые соответствуют текущей записи.

Метод eachWithIndex() также предоставляет индекс в дополнение к Entry. Оба метода принимают Closure в качестве аргумента.

В следующем примере мы перебираем каждую запись. Замыкание, переданное методу each(), получает пару ключ-значение из записи неявного параметра и выводит ее:

Затем мы используем метод eachWithIndex() для вывода текущего индекса вместе с другими значениями: ~ ~~

map.each{entry -> println "$entry.key: $entry.value"}

Также можно попросить, чтобы ключ, значение и индекс предоставлялись отдельно:

map.eachWithIndex{entry, i -> println "$i $entry.key: $entry.value"}

7. Фильтрация

map.eachWithIndex{key, value, i -> println "$i $key: $value"}

Мы можем использовать методы find(), findAll() и grep() для фильтровать и искать записи карты на основе ключей и значений.

Давайте начнем с определения карты, на которой будут выполняться эти методы:

Во-первых, мы рассмотрим метод find(), который принимает замыкание и возвращает первую запись, соответствующую условию замыкания:

def map = [name:"Jerry", age: 42, city: "New York", hobby:"Singing"]

~ ~~ Точно так же findAll также принимает замыкание, но возвращает карту со всеми парами ключ-значение, которые удовлетворяют условию в замыкании:

assertTrue(map.find{it.value == "New York"}.key == "city")

Если мы предпочитаем использовать список, мы можем использовать grep вместо findAll:

assertTrue(map.findAll{it.value == "New York"} == [city : "New York"])

Сначала мы использовали grep для поиска записей, имеющих значение New York. Затем, чтобы продемонстрировать, что тип возвращаемого значения — List, мы перебираем результат grep(). И для каждой записи в списке, которая доступна в неявном параметре, мы проверяем, является ли это ожидаемым результатом.

map.grep{it.value == "New York"}.each{it -> assertTrue(it.key == "city" && it.value == "New York")}

Затем, чтобы узнать, удовлетворяют ли все элементы карты условию, мы можем использовать каждый, который возвращает логическое значение.

Давайте проверим, все ли значения в карте имеют тип String:

Аналогично, мы можем использовать any, чтобы определить, соответствуют ли какие-либо элементы карты условию:

assertTrue(map.every{it -> it.value instanceof String} == false)

8. Преобразование и Сбор

assertTrue(map.any{it -> it.value instanceof String} == true)

«Иногда нам может понадобиться преобразовать записи на карте в новые значения. Используя методы collect() и collectEntries(), можно преобразовывать и собирать записи в коллекцию или карту соответственно.

Давайте рассмотрим несколько примеров.

Учитывая карту идентификаторов сотрудников и сотрудников:

Мы можем собрать имена всех сотрудников в список, используя collect():

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

Далее, если нас интересует уникальный набор имен, мы можем указать коллекцию, передав объект Collection:

def names = map.collect{entry -> entry.value.name}
assertTrue(names == ["Jerry", "Long", "Dustin", "Dustin"])

Если мы хотим изменить имена сотрудников на карте со строчных на прописные, мы можем использовать collectEntries. Этот метод возвращает карту преобразованных значений:

def uniqueNames = map.collect([] as HashSet){entry -> entry.value.name}
assertTrue(uniqueNames == ["Jerry", "Long", "Dustin"] as Set)

Наконец, также можно использовать методы collect в сочетании с методами find и findAll для преобразования отфильтрованных результатов:

def idNames = map.collectEntries{key, value -> [key, value.name]}
assertTrue(idNames == [1:"Jerry", 2:"Long", 3:"Dustin", 4:"Dustin"])

Здесь мы сначала находим все сотрудников в возрасте от 20 до 30 лет и собрать их на карту.

def below30Names = map.findAll{it.value.age < 30}.collect{key, value -> value.name}
assertTrue(below30Names == ["Long", "Dustin"])

9. Группировка

Иногда нам может понадобиться сгруппировать некоторые элементы карты в подкарты на основе условия.

Метод groupBy() возвращает карту карт. И каждая карта содержит пары ключ-значение, которые дают один и тот же результат для заданного условия:

Другой способ создания подкарт — использование subMap(). В groupBy() она отличается тем, что позволяет группировать только по ключам:

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

В этом случае записи для ключей 1 и 2 возвращаются в новой карте, а все остальные записи отбрасываются.

def keySubMap = map.subMap([1,2])
assertTrue(keySubMap == [1:20, 2:40])

10. Сортировка

Обычно при сортировке мы можем захотеть отсортировать записи на карте по ключу, значению или тому и другому. Groovy предоставляет метод sort(), который можно использовать для этой цели.

Учитывая карту:

Если необходимо выполнить сортировку по ключу, используйте метод sort() без аргументов, основанный на естественном упорядочении:

def map = [ab:20, a: 40, cb: 11, ba: 93]

Или используйте sort(Comparator ) для обеспечения логики сравнения:

def naturallyOrderedMap = map.sort()
assertTrue([a:40, ab:20, ba:93, cb:11] == naturallyOrderedMap)

Далее, для сортировки либо по ключу, либо по значениям, либо по обоим, мы можем указать условие закрытия для sort():

def compSortedMap = map.sort({k1, k2 -> k1 <=> k2} as Comparator)
assertTrue([a:40, ab:20, ba:93, cb:11] == compSortedMap)

11. Заключение

def cloSortedMap = map.sort({it1, it2 -> it1.value <=> it1.value})
assertTrue([cb:11, ab:20, a:40, ba:93] == cloSortedMap)

Мы начали посмотрев, как мы можем создавать Карты в Groovy. Далее мы рассмотрели различные способы добавления, извлечения и удаления элементов с карты.

Позже мы рассмотрели методы выполнения общих операций, которые поставляются в Groovy из коробки. Они включали фильтрацию, поиск, преобразование и сортировку.

Как всегда, примеры, рассмотренные в статье, можно найти на GitHub.

«