Подумайте об упрощении вашего примера.Вместо вложения итераций вы можете использовать операцию Groovy inject {}
, которая похожа на популярную операцию сворачивания влево - она выполняет итерацию по списку и накапливает результат в другой форме.Взгляните на этот пример:
import groovy.util.slurpersupport.NodeChild
import java.math.RoundingMode
def input = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<SearchRS>
<SearchStatus>SUCCESS</SearchStatus>
<Itinerary>
<Name>Joe</Name>
<Ticket>111.11</Ticket>
<Taxes>1.11</Taxes>
</Itinerary>
<Itinerary>
<Name>Bob</Name>
<Ticket>222.22</Ticket>
<Taxes>2.22</Taxes>
</Itinerary>
<Itinerary>
<Name>Joe</Name>
<Ticket>333.33</Ticket>
<Taxes>3.33</Taxes>
</Itinerary>
<Itinerary>
<Name>Bob</Name>
<Ticket>444.44</Ticket>
<Taxes>4.44</Taxes>
</Itinerary>
<Itinerary>
<Name>Joe</Name>
<Ticket>0.0</Ticket>
<Taxes>0.0</Taxes>
</Itinerary>
</SearchRS>'''
def xml = new XmlSlurper().parseText(input)
def result = xml.'*'.findAll { node ->
node.name() == 'Itinerary'
}.inject([:]) { Map<String, List<BigDecimal>> map, NodeChild node ->
// sum ticket + tax
def value = node.Ticket.text().toBigDecimal() + node.Taxes.text().toBigDecimal()
// collect all values as Name => List of prices
map.merge(node.Name.text(), [value], { a, b -> a + b })
return map
}.collectEntries { String k, List<BigDecimal> v ->
// calculate average price per name and round the final result
[(k): (v.sum() / v.size()).setScale(2, RoundingMode.HALF_UP)]
}
println result
Сначала мы фильтруем все дочерние узлы, чтобы работать только с узлами Itinerary
.Затем мы вызываем inject([:])
, который начинается с пустой карты, и мы начинаем итерацию по всем узлам.Каждый шаг итерации:
- вычисляет значение путем добавления значений
Ticket
и Taxes
и сохраняет его как BigDecimal
- , которое вызывает
Map.merge(key, value, remappingFunction)
, поэтому, если данный ключ не существует, он помещает ключ со значением, сохраненным в списке.Если оно существует, мы добавляем значение в существующий список (объединяя новое значение, упакованное в список) - возвращает карту, поэтому в следующей итерации измененная карта используется как
map
переменная
В самом конце мы вызываем collectEntries
метод, который преобразует исходную карту - он преобразует Map<String, List<BigDecimal>>
в Map<String, BigDecimal>
, где значение, сохраненное как BigDecimal
, является средним значением, вычисленным из значений, хранящихся в списке,Когда мы вычисляем окончательное значение, мы можем вызвать BigDecimal.setScale(scale, mode)
, чтобы округлить его.
Когда вы запустите этот пример, вы получите что-то вроде этого:
[Joe:149.63, Bob:336.66]
Для Java 7
Java 8 представила этот Map.merge(key, value, remappingFunction)
полезный метод.Однако это может быть выражено более императивно, например:
def key = node.Name.text()
if (!map.containsKey(key)) {
map.put(key, [])
}
map.put(key, map.get(key) << value)
Если вы используете Java более раннюю, чем Java 8, используйте эти 4 строки вместо map.merge(...)
.
Надеюсь, это поможет.