使用 java 中的流和 lambda 在(树)图中查找所有最大值

Finding all max values in (tree)map with streams and lambda in java

所以我必须在 java 的地图中使用流和 lambda 找到最大值。找到一个最大值不是问题,但如何找到多个呢? 例子: 包含元素“e”= 2、“i”= 1、“a”= 2 的 Treemap, 我目前的解决方案给了我“a”= 2,但我想要“a”= 2,“e”= 2

我的代码:

Map<String, Integer> frequencies = new Treemap<>();
frequencies.put("e", 2);//I don't put the values in like this but it'll do to test
frequencies.put("i", 1);
frequencies.put("a", 2);
Optional<Map.Entry<String, Integer>> maxEntry = frequencies.entrySet().stream()
        .max(Map.Entry.comparingByValue());//frequencies is the TreeMap<String, Integer>
//I know this only searches one max value, here's my other attempt:
try (Stream<Map.Entry<String, Integer>> stream = frequencies.entrySet().stream()) {
      stream
          .sorted(Map.Entry.comparingByValue())
          .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (k, v) -> k, LinkedHashMap::new));
//I don't know what to do here, I somehow would have to get the result into a new list, but it still only returns one result
    }

如果我做错了什么,请告诉我。

首先,找到最大频率:

Optional<Integer> maxFreqOptional = frequencies.values()
    .stream()
    .max(Comparator.naturalOrder());

那么你可以只收集所有具有这个频率的条目作为一个值:

Integer maxFreq = maxFreqOptional.get(); // check if it's empty first
List<String> mostFrequent = frequencies.entrySet()
    .stream()
    .filter(entry -> entry.getValue().equals(maxFreq))
    .map(Map.Entry<String, Integer>::getKey)
    .collect(Collectors.toList());

你快完成了:

使用值(=频率)作为键,使用原始键作为值,将入口流收集到 TreeMap 中。因此使用级联groupingBy。然后取key最大的条目:

TreeMap<Integer, List<String>> map = frequencies.entrySet().stream()
    .collect(Collectors.groupingBy(
        Map.Entry<String, Integer>::getValue, TreeMap::new, Collectors.toList()
    ));
Map.Entry<Integer, List<String>> largest = map.lastEntry();

注意:如果你的数据集很大并且你想避免建立这个反向映射,你可能更喜欢其他建议的迭代映射两次的解决方案之一:一次找到最大的频率,然后再次查找所有对应的条目。

您可以按 maxEntry 的值进行过滤(如果存在)以获得所有 Map.Entry 的最大值

List<Map.Entry<String, Integer>> res = frequencies.entrySet().stream()
                                 .filter(e -> e.getValue().equals(maxEntry.get().getValue()))
                                 .collect(Collectors.toList());

在线演示here

输出:[a=2, e=2]

So I have to find the max value(s) in a map in java with streams and lambda.

这是一种不用 TreeMap 的方法。它对包含条目的频率进行计数。

Map<String, Integer> map = Map.of("z", -1, "b", 0, "r", -2,
        "s", 0, "j", 1, "a", 2, "i", 1, "e", 2);

Optional<Entry<Integer, List<Entry<String,Integer>>>> opt = map.entrySet()
        .stream()
        .collect(Collectors.groupingBy(Entry::getValue))
        .entrySet().stream().max(Entry.comparingByKey());

System.out.println(opt.isPresent() ? opt.get().getValue() : "Empty List");

版画

[a=2, e=2]

为了好玩,您可以绕过初始地图并创建条目流。实际上,当您创建初始 Map 时,您正在创建 Entry 对象,因此此处不涉及额外的 map 开销。

        
Builder<Entry<String, Integer>> entryStream = Stream.builder();

entryStream.add(Map.entry("b", 0));
entryStream.add(Map.entry("r", -2));
entryStream.add(Map.entry("s", 0));
entryStream.add(Map.entry("j", 1));
entryStream.add(Map.entry("a", 2));
entryStream.add(Map.entry("i", 1));
entryStream.add(Map.entry("e", 2));

此时,除了流已准备好调用外,与之前相同。

Optional<Entry<Integer, List<Entry<String, Integer>>>> opt =
                entryStream.build()
                        .collect(Collectors
                                .groupingBy(Entry::getValue))
                        .entrySet().stream()
                        .max(Entry.comparingByKey());
        
System.out.println(opt.isPresent() ? opt.get().getValue() :
                "Empty List");
        

像以前一样打印

[a=2, e=2]

由于您只需要匹配最大值的条目,因此将映射的所有条目分组到反向映射中没有任何好处。您只需要丢弃所有值与最大值不匹配的条目。

这是使用 lambda 表达式的简洁方法:

Integer max = frequencies.isEmpty() ? 
              Integer.MIN_VALUE :
              Collections.max(frequencies.values());

List<String> result = new ArrayList<>();
frequencies.forEach((k, v) -> { if (v.equals(max)) result.add(k); });

所以,在 max 中你有最大值,而在 result 中你有匹配最大值的键。该算法的时间复杂度是 O(n) 最坏情况,不像分组条目的算法,它们都是 O(nlogn).

流等效版本更详细:

Integer max = frequencies.values().stream()
    .max(Comparator.naturalOrder())
    .orElse(Integer.MIN_VALUE);

List<String> result = frequencies.entrySet().stream()
    .filter(e -> e.getValue().equals(max))
    .map(Map.Entry::getKey)
    .collect(Collectors.toList());