Java 8流列表<foo>到带有条件分组的Map <date,map <string,long =“” >>

时间:2018-11-01 12:51:42

标签: java java-8 java-stream

以下课程:

public class Foo {
    private Date date;
    private String name;
    private Long number;
}

我现在有一个List<Foo>,我想将其转换为Map<Date, Map<String,Long>>Long应该是numbers的总和)。很难做到的是,我希望内部地图中恰好有26个条目,其中第26个条目称为“其他”,它汇总了所有数字都比其他25个数字小的数字。

我想出了以下代码:

data.stream().collect(Collectors.groupingBy(e -> e.getDate(), Collectors.groupingBy(e -> {
    if (/*get current size of inner map*/>= 25) {
        return e.getName();
    } else {
        return "Other";
    }

}, Collectors.summingLong(e -> e.getNumber()))));

如您所见,我不知道如何检查内部映射中已经存在的元素数量。如何获得内部地图的当前大小,或者还有另一种方法来实现我想要的?

我的Java 7代码:

Map<Date, Map<String, Long>> result = new LinkedHashMap<Date, Map<String, Long>>();
for (Foo fr : data) {
    if (result.get(fr.getDate()) == null) {
        result.put(fr.getDate(), new LinkedHashMap<String, Long>());
    }
    if (result.get(fr.getDate()) != null) {
        if (result.get(fr.getDate()).size() >= 25) {
            if (result.get(fr.getDate()).get("Other") == null) {
                result.get(fr.getDate()).put("Other", 0l);
            }
            if (result.get(fr.getDate()).get("Other") != null) {
                long numbers= result.get(fr.getDate()).get("Other");
                result.get(fr.getDate()).replace("Other", numbers+ fr.getNumbers());
            }
        } else {
            result.get(fr.getDate()).put(fr.getName(), fr.getNumbers());
        }
    }
}

编辑:

地图应该可以帮助我实现如下表格:

enter image description here

但是我需要先总结“其他”。


如果您需要更多信息,请随时询问

3 个答案:

答案 0 :(得分:10)

我认为使用Stream API不会使此操作受益。不过,您可以使用Java 8功能来改善操作:

Map<Date, Map<String, Long>> result = new LinkedHashMap<>();
for(Foo fr : data) {
    Map<String, Long> inner
      = result.computeIfAbsent(fr.getDate(), date -> new LinkedHashMap<>());
    inner.merge(inner.size()>=25?"Other":fr.getAirlineName(), fr.getNumbers(), Long::sum);
}

此代码假定航空公司名称在每个日期都已经是唯一的。否则,您将不得不将代码扩展到

Map<Date, Map<String, Long>> result = new LinkedHashMap<>();
for(Foo fr : data) {
    Map<String, Long> inner
      = result.computeIfAbsent(fr.getDate(), date -> new LinkedHashMap<>());
    inner.merge(inner.size() >= 25 && !inner.containsKey(fr.getAirlineName())?
      "Other": fr.getAirlineName(), fr.getNumbers(), Long::sum);
}

正确累积航空公司的值。


为完整起见,这是将其实现为流操作的方法。

由于自定义收集器有些复杂,因此值得将其编写为可重用的代码:

public static <T,K,V> Collector<T,?,Map<K,V>> toMapWithLimit(
    Function<? super T, ? extends K> key, Function<? super T, ? extends V> value,
    int limit, K fallBack, BinaryOperator<V> merger) {

    return Collector.of(LinkedHashMap::new, (map, t) ->
            mergeWithLimit(map, key.apply(t), value.apply(t), limit, fallBack, merger),
            (map1,map2) -> {
                if(map1.isEmpty()) return map2;
                if(map1.size()+map2.size() < limit)
                    map2.forEach((k,v) -> map1.merge(k, v, merger));
                else
                    map2.forEach((k,v) ->
                        mergeWithLimit(map1, k, v, limit, fallBack, merger));
                return map1;
            });
}
private static <T,K,V> void mergeWithLimit(Map<K,V> map, K key, V value,
    int limit, K fallBack, BinaryOperator<V> merger) {
    map.merge(map.size() >= limit && !map.containsKey(key)? fallBack: key, value, merger);
}

这类似于Collectors.toMap,但是支持一个限制和一个用于其他条目的后备键。您可能会认识到Map.merge调用,类似于循环解决方案是关键要素。

然后,您可以将收集器用作

Map<Date, Map<String, Long>> result = data.stream().collect(
    Collectors.groupingBy(Foo::getDate, LinkedHashMap::new,
        toMapWithLimit(Foo::getAirlineName, Foo::getNumbers, 25, "Other", Long::sum)));

答案 1 :(得分:1)

:为时已晚:)但是,我使用了Java 8解决方案,而没有使用for循环或自定义收集器。它基于collectingAndThen,可让您转换收集操作的结果。

它允许我根据阈值在整理器操作中划分流。

但是,我不确定性能。

 int treshold = 25


 Map<Date, Map<String, Long>> result = data.stream().collect(groupingBy(Foo::getDate,
            collectingAndThen(Collectors.toList(), x -> {
                if (x.size() >= treshold) {
                    Map<String, Long> resultMap = new HashMap<>();
                    resultMap.putAll(x.subList(0, treshold).stream().collect(groupingBy(Foo::getName, Collectors.summingLong(Foo::getNumber))));
                    resultMap.putAll(x.subList(treshold, x.size()).stream().collect(groupingBy(y -> "Other", Collectors.summingLong(Foo::getNumber))));
                    return resultMap;
                } else {
                    return x.stream().collect(groupingBy(Foo::getName, Collectors.summingLong(Foo::getNumber)));
                }
            })));

答案 2 :(得分:1)

首先,让我们通过不使用Streams使其适应Java 8来简化原始问题。

Map<Date, Map<String, Long>> result = new LinkedHashMap();
for (Foo fr : data) {
    Map<String, Long> map = result.getOrDefault(fr.getDate(), new LinkedHashMap());
    if (map.size() >= 25) {
        Long value = map.getOrDefault("Other", 0L); // getOrDefault from 1.8
        map.put("Other", value + 1);
    } else {
        map.put(fr.getName(), fr.getNumber());
    }
    result.put(fr.getDate(), map);
}

现在使用Stream

int limit = 25;
Map<Date, Map<String, Long>> collect = data.stream()
    .collect(Collectors.groupingBy(Foo::getDate))
    .entrySet().stream()
    .collect(Collectors.toMap(Map.Entry::getKey, v -> {
        Map<String, Long> c = v.getValue().stream()
                .limit(limit)
                .collect(Collectors.toMap(Foo::getName, Foo::getNumber));
        long remaining = v.getValue().size() - limit;
        if (remaining > 0) {
            c.put("Other", remaining);
        }
        return c;
    }));