spring Java流是否按字段分组并按此字段计数排序?

4uqofj5v  于 2022-11-28  发布在  Spring
关注(0)|答案(1)|浏览(125)

我正在尝试通过使用Java流在以下实体上获取具有最大城市数的3个国家的国家代码:

城市:

id   | name     | countryCode |
------------------------------
1    | Berlin   | DE          |
2    | Munich   | DE          |
3    | Köln     | DE          |
4    | Paris    | FR          |
5    | Kopenhag | DK          |
...

我尝试了一些东西,但没有像预期的那样工作。那么,什么是最合适的方法来获得前3个国家代码(前3个大多是重复的国家代码)呢?

final Map<String, City> airportCodes2 = airportService.getCities().stream()
                .map(City::getCountryCode, Function.identity())
                .toMap();
0yg35tkg

0yg35tkg1#

排序

最简单的方法是通过生成Map<String, Long>类型的辅助Map,将每个countryCode与城市计数相关联,从而按countryCode对数据进行分组。
然后在Map条目上生成流,并按 Value 的相反顺序对其进行排序(* 即,按城市计数的降序 *)。
如何实现:

public static List<String> getTopNCodes(List<City> cities,
                                        int limit) {
    return cities.stream()
        .collect(Collectors.groupingBy( // creates a Map<String, Long>
            City::getCountryCode,
            Collectors.counting()
        ))
        .entrySet().stream()
        .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
        .limit(limit) // <- retain top N
        .map(Map.Entry::getKey)
        .toList();
}

这种方法的时间复杂度是O(n * log n)**,如果要检索的元素数量很小(如3),同时数据量很大,这将是不利的。
我们可以做得更好。

使用自定义收集器进行部分排序

我们可以使用PriorityQueue(该类是JDK提供的 Binary Heap 的实现)执行部分排序,而不是对辅助Map的所有条目进行排序。
为此,我们可以使用静态工厂方法Collector.of()实现一个自定义收集器。
PriorityQueue的示例将用作收集器的可变容器。来自流的每个Map条目将与堆的 root 元素进行比较。如果元素大于此值(条目包含较大的计数),则将下一个条目添加到队列根。如果超出限制,则应删除根元素。
为了使代码可重用,我们可以将其gentrify化。第一部分(创建一个中间Map,其中的值表示键的频率)保持不变。

public static <T, K> List<K> getTopN(List<T> list,
                                          Function<T, K> keyExtractor,
                                          int limit) {
    return list.stream()
        .collect(Collectors.groupingBy(
            keyExtractor,
            Collectors.counting()
        ))
        .entrySet().stream()
        .collect(getMaxN(
            limit,
            Map.Entry.<K, Long>comparingByValue().reversed(),
            Map.Entry::getKey
        ));
}

最小基于堆的收集器数:

public static <T, R> Collector<T, ?, List<R>> getMaxN(int size,
                                                      Comparator<T> comparator,
                                                      Function<T, R> keyExtractor) {
    
    return Collector.of(
        () -> new PriorityQueue<>(comparator),
        (Queue<T> queue, T next) -> tryAdd(queue, next, comparator, size),
        (Queue<T> left, Queue<T> right) -> {
            right.forEach(next -> tryAdd(left, next, comparator, size));
            return left;
        },
        (Queue<T> queue) -> queue.stream().map(keyExtractor).toList(),
        Collector.Characteristics.UNORDERED
    );
}

public static <T> void tryAdd(Queue<T> queue, T next, Comparator<T> comparator, int size) {
    if (queue.size() == size && comparator.compare(queue.element(), next) < 0)
        queue.remove(); // if next value is greater than the smallest element in the queue and max size has been exceeded the smallest element needs to be removed from the queue
    if (queue.size() < size) queue.add(next);
}

相关问题