Округление чисел с плавающей запятой при построении карты - PullRequest
0 голосов
/ 21 мая 2018

Я чередую ответ XMP следующим образом:

 <?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(xmlString)
 def map = [:]
 xml.'**'.findAll {it.name() == 'Name'}.unique().each { name -> 
    map[name] = xml.'**'.findAll {it.name() == 'Itinerary' && name == it.Name.text() }.collect { Double.parseDouble(it.Ticket.text()) + Double.parseDouble(it.Taxes.text())}.findAll {it}.with { sum() / size() }
}

Это дает мне результат, который выглядит следующим образом:

 [Joe:10.9101234, Bob:20.319999999999997]

Я хотел бы округлить его, чтобы выглядеть так, но я не знаюгде разместить метод round (2).Звоните:

 [Joe:10.91, Bob:20.31]

Любая помощь приветствуется!

Ответы [ 2 ]

0 голосов
/ 22 мая 2018

В качестве небольшого упрощения ответа Шимона мы можем напрямую использовать метод toBigDecimal node (экземпляр NodeChild) и заменить слияние вызовом withDefault исходной карты:

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.Itinerary.inject([:].withDefault{[]}) { m, n ->
  m[n.Name] << n.Ticket.toBigDecimal() + n.Taxes.toBigDecimal()
  m
}.collectEntries { name, v ->
  [(name): (v.sum() / v.size()).setScale(2, RoundingMode.HALF_UP)]
}

println result

или, возможно, еще проще с groupBy по имени сначала:

result = xml.Itinerary.groupBy { it.Name }.collectEntries { k, v -> 
  def c = v.collect { it.Ticket.toBigDecimal() + it.Taxes.toBigDecimal() }
  [k, (c.sum() / c.size).setScale(2, RoundingMode.HALF_UP)]
}

печатает:

~> groovy test.groovy 
[Joe:149.63, Bob:336.66]

, если я правильно понял проблему.

0 голосов
/ 21 мая 2018

Подумайте об упрощении вашего примера.Вместо вложения итераций вы можете использовать операцию 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(...).

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...