对String数组进行分组依据和求和运算

时间:2015-04-21 18:12:15

标签: java arrays dictionary

我有一个String[] dataValues,如下所示:

ONE:9
TWO:23
THREE:14
FOUR:132
ONE:255
TWO:727
FIVE:3
THREE:196
FOUR:1843
ONE:330
TWO:336
THREE:190
FOUR:3664

我想总计ONETWOTHREEFOURFIVE的值。

所以我为同一个创建了HashMap

Map<String, Integer> totals = new HashMap<String, Integer>();
for(String dataValue : dataValues){
    String[] keyVal = dataValue.split(":");
      totals.put(keyVal[0], totals.get(keyVal[0]).intValue() + Integer.parseInt(keyVal[1]));                            
}

但如果地图上尚未存在该密钥,则上面的代码显然会抛出以下异常:

Exception in thread "main" java.lang.NullPointerException

在上面的用例中获取总计的最佳方法是什么?

3 个答案:

答案 0 :(得分:5)

您可以获取给定键的值并检查它是否为空:

for(String dataValue : dataValues){
    String[] keyVal = dataValue.split(":");
    Integer i = totals.get(keyVal[0]);
    if(i == null) {
        totals.put(keyVal[0], Integer.parseInt(keyVal[1]));
    } else {
        totals.put(keyVal[0], i + Integer.parseInt(keyVal[1]));
    }
 }

  

在上面的用例中获取总计的最佳方法是什么?

使用Java 8,您可以使用合并功能

for(String dataValue : dataValues){
    String[] keyVal = dataValue.split(":");
    totals.merge(keyVal[0], Integer.parseInt(keyVal[1]), Integer::sum);
}

这个功能有什么作用?让我们引用文档:

  

如果指定的键尚未与值关联或是   与null关联,将其与给定的非null值相关联。   否则,将相关值替换为给定的结果   重新映射函数,如果结果为空则删除

因此,当你得到它时,如果没有与该键相关联的值,则只需将其映射为keyVal[1]的int值。如果已经存在,则需要提供一个函数来决定对两个值(已映射的值和要映射的值)执行的操作。

在你的情况下你想要求它们,所以这个函数看起来像(a, b) -> a + b,可以用方法引用Integer.sum代替,因为它是一个带两个int并返回一个int的函数,所以一个有效的候选人(当然还有你需要的语义)。

<小时/> 但是等等,我们实际上可以做得更好!这就是Stream API和收集器类的用武之地。

从文件中获取Stream<String>,将每一行拆分为一个数组,按每个数组的第一个元素(键)对每个数组进行分组,将其第二个元素(值)映射为整数并对它们求和:

import static java.util.stream.Collectors.*;

...

Map<String, Integer> map = Files.lines(Paths.get("file"))
    .map(s -> s.split(":"))
    .collect(groupingBy(arr -> arr[0], summingInt(arr -> Integer.parseInt(arr[1])));

另一种方法是使用toMap收集器。

.collect(toMap(arr -> arr[0], arr -> Integer.parseInt(arr[1]), Integer::sum));

在相同的Stream<String[]>中,您会在Map<String, Integer>中收集密钥为arr[0]的结果,其值为arr[1]所持有的int值。如果您具有相同的键,则通过将它们相加来合并值。

两者都给出相同的结果,我喜欢第一个结果,因为使用收集器的名称,它使得意图明确表示您正在对元素进行分组,但这取决于您选择。

一开始可能有点难以理解,但是一旦你掌握了这些(下游)收藏家的概念,它就会非常强大。

希望它有所帮助! :)

答案 1 :(得分:4)

由于Java 8而不是map.get,您可以使用map.getOrDefault,如果缺少数据,将返回您定义的默认数据

totals.getOrDefault(keyVal[0], 0).intValue()

答案 2 :(得分:3)

这是一个优雅的(编辑:pre Java 8)解决方案:

Integer storedVal = hashMap.get(str);  

String str = keyVal[0];
int num = Integer.parseInt(keyVal[1]);

hashMap.put(str, storedVal == null ? num : storedVal + num);

检查密钥是否存在。如果没有,请使用保持的int。

创建它

如果密钥确实存在,则检索该值并进行数学运算,存储总和。

这是有效的,因为如果一个键已经存在,'put'将覆盖该值。