使用Java 8合并,排序和限制Map流

时间:2018-03-21 14:04:39

标签: java java-8 java-stream

我有两张地图Map<String, Long>。我想合并两个映射,按降序排序,并获得前5个。如果合并中有重复键,我需要对值求和。我有以下代码可用:

Map<String, Long> topFive = (Stream.concat(map1.entrySet().stream(), 
                                           map2.entrySet().stream())
                                   .collect(Collectors.toMap(Map.Entry::getKey, 
                                                             Map.Entry::getValue,
                                                             Long::sum)))
                                   .entrySet()
                                   .stream()
                                   .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                                   .limit(5)
                                   .collect(Collectors.toMap(Map.Entry::getKey,
                                                             Map.Entry::getValue,
                                                            (v1, v2) -> v1,
                                                            LinkedHashMap::new));

但我想知道是否有更好的解决方案。

4 个答案:

答案 0 :(得分:3)

如果你的意思是更好 性能,并且你有大量的集合,并且只需要很少的顶级元素,你可以避免对整个地图进行排序,给定{{ 1}}复杂性。

如果您已经拥有Guava,则可以使用MinMaxPriorityQueue仅存储最佳的 N 结果。然后只需对这几个常量 N 元素进行排序。

n*log(n)

如果您没有/想要使用Guava,可以使用自定义Comparator<Entry<String, Long>> comparator = Entry.comparingByValue(reverseOrder()); Map<String, Long> merged = Stream.of(map1, map2) .map(Map::entrySet) .flatMap(Set::stream) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Long::sum)); MinMaxPriorityQueue<Entry<String, Long>> tops = MinMaxPriorityQueue.orderedBy(comparator) .maximumSize(5) .create(merged.entrySet()); Map<String, Long> sorted = tops.stream() .sorted(comparator) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (m1, m2) -> m1, LinkedHashMap::new)); 来模拟MinMaxPriorityQueue(如果您还可以创建在构造函数中接收最大大小的类,不想使用匿名类[这是为了显示功能]。

TreeMap

并将所有元素添加到顶部。

Set<Entry<String, Long>> sorted = new TreeSet<Entry<String, Long>>(comparator) {
    @Override
    public boolean add(Entry<String, Long> entry) {
        if (size() < 5) { // 5 can be constructor arg in custom class
            return super.add(entry);
        } else if (comparator().compare(last(), entry) > 0) {
            remove(last());
            return super.add(entry);
        } else {
            return false;
        }
    }
};

您还可以更改合并功能,使用类似于Federico提到的合并的内容。

sorted.addAll(merged);

这比使用流更快,在此之后,一旦你有了合并的地图,你可以用Map<String, Long> merged = new HashMap<>(map1); map2.forEach((k, v) -> merged.merge(k, v, Long::sum)); MinMaxPriorityQueue选择前N个元素,再次避免不必要的排序整个系列。

答案 1 :(得分:0)

更好的解决方案可能是使用保持前5个的累加器,而不是对整个流进行排序。现在你正在进行估计的n * log(n)比较,而不是n和n * log(5)之间的某种比较。

答案 2 :(得分:0)

我会专注于让代码更容易阅读:

// Merge
Map<String, Long> merged = new HashMap<>(map1);
map2.forEach((k, v) -> merged.merge(k, v, Long::sum));

// Sort descending
List<Map.Entry<String, Long>> list = new ArrayList<>(merged.entrySet());
list.sort(Map.Entry.comparingByValue(Comparator.reverseOrder()));

// Select top entries
Map<String, Long> top5 = new LinkedHashMap<>();
list.subList(0, Math.min(5, list.size()))
    .forEach(e -> e.put(e.getKey(), e.getValue()));

此外,通过不使用流,此解决方案肯定会有更好的性能。

答案 3 :(得分:0)

使用Collector添加其他解决方案。它使用TreeSet作为中间累积类型,使用整理器将集合转换为地图。

private <K, V, E extends Map.Entry<K,V>> Collector<E, TreeSet<E>, Map<K,V>> 
        toMap(BinaryOperator<V> mergeFunction, Comparator<E> comparator, int limit) {
    Objects.requireNonNull(mergeFunction);
    Objects.requireNonNull(comparator);

    Supplier<TreeSet<E>> supplier = () -> new TreeSet<>(comparator);
    BiConsumer<TreeSet<E>, E> accumulator = (set, entry) -> accumulate(set, entry, mergeFunction);
    BinaryOperator<TreeSet<E>> combiner = (destination, source) -> {
            source.forEach(e -> accumulator.accept(destination, e)); return destination; };
    Function<TreeSet<E>, Map<K,V>> finisher = s -> s.stream()
            .limit(limit)
            .collect(Collectors.toMap(E::getKey, E::getValue, (v1, v2) -> v1, LinkedHashMap::new));

    return Collector.of(supplier, accumulator, combiner, finisher);
}

private <K, V, E extends Map.Entry<K,V>> void accumulate(
        TreeSet<E> set, E newEntry, BinaryOperator<V> mergeFunction) {
    Optional<E> entryFound = set.stream()
            .filter(e -> Objects.equals(e.getKey(), newEntry.getKey()))
            .findFirst();

    if (entryFound.isPresent()) {
        E existingEntry = entryFound.get();
        set.remove(existingEntry);
        existingEntry.setValue(mergeFunction.apply(existingEntry.getValue(), newEntry.getValue()));
        set.add(existingEntry);
    }
    else {
        set.add(newEntry);
    }
}

这是你如何使用它,比较条目的值(反过来)和使用Long::sum合并函数进行条目冲突。

Comparator<Map.Entry<String,Long>> comparator = Map.Entry.comparingByValue(Comparator.reverseOrder());
Map<String, Long> topFive = Stream.of(map1, map2)
        .map(Map::entrySet)
        .flatMap(Collection::stream)
        .collect(toMap(Long::sum, comparator, 5));