在构建地图时舍入浮点数

时间:2018-05-21 16:52:38

标签: groovy

我正在循环遍历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]

我想把它看起来像这样,但我不知道在哪里放置圆(2)方法。拨打:

 [Joe:10.91, Bob:20.31]

任何帮助表示赞赏!

2 个答案:

答案 0 :(得分:0)

考虑简化您的示例。您可以使用与常见的fold-left操作类似的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([:]),它以空映射开始,然后我们开始迭代所有节点。迭代的每一步都是:

  • 通过添加TicketTaxes值来计算值,并将其存储为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(...)

希望它有所帮助。

答案 1 :(得分:0)

作为Szymon的答案的略微简化,我们可以直接使用toBigDecimal(NodeChild实例)的node方法,我们可以用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]

假设我已正确理解问题。