在构建地图时对浮点数进行舍入



我正在循环访问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]

任何帮助表示赞赏!

考虑简化您的示例。您可以使用 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(...)

希望对您有所帮助。

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

假设我已经正确理解了问题。

最新更新