【问题标题】:Extract keys from Map for which there is no duplicate values从没有重复值的 Map 中提取键
【发布时间】:2019-09-06 09:16:42
【问题描述】:

地图定义为:

 Map<Integer,String> map = new HashMap<>();
        map.put(2,"ram");
        map.put(3,"ram");
        map.put(4,"gopal");
        map.put(5,"madan");
        map.put(6,"shyam");
        map.put(7,"gopal");
        map.put(8,"ram");

我的预期输出是 List,其中仅包含没有重复值的键。

5
6

我的方法和思考过程:

思考过程一:

我会采用map.entrySet().stream().map(....),然后在 map 中采用另一个流并过滤存在重复值的值。

这种方法很快就被浪费了,因为第一个索引值将在嵌套流中再次进行比较,因此我碰巧过滤掉了所有元素。

思维过程2

我将值保存在不同的 List 中:

List<String> subList = map.entrySet().stream()
        .map((k)->k.getValue())
        .collect(Collectors.toList());

然后:

    map.entrySet().stream()
            .filter(s ->
                subList.contains(s.getValue())                )
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());

但我得到的输出为

2
3
4
5
6
7
8

输出很明显,因为我从流中选择 s 的值我在池中比较它,该值将始终存在至少一次。

然后我再次想到,如果我可以有一个计数计数的计数器,并且如果该值存在,那么它将增加,但现在一切似乎都非常模糊。

我可以使用流通过索引进行迭代的任何方式,这样我就可以始终保留我正在使用的键值并与其余值进行比较。

希望得到一个简短的解释。

【问题讨论】:

    标签: java java-8


    【解决方案1】:

    您可以将任务分为两步。 groupingBy()counting() 收集器重复的第一个计数值。

    Map<String,Long> valueCount = map.values()
               .stream()
               .collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));
    

    它的结果是:

    {madan=1, shyam=1, gopal=2, ram=3}

    第二步是只找到它们的值不重复的键。所以要实现这一点,您可以使用filter() 并通过上一步结果过滤地图。

    map.entrySet()
       .stream()
       .filter(entry -> valueCount.get(entry.getValue())==1).map(Map.Entry::getKey)
       .collect(Collectors.toList())
    

    【讨论】:

    • 你不需要计算。一个Boolean告诉一个值是否唯一就够了:Map&lt;String,Boolean&gt; unique = map.values().stream() .collect(Collectors.toMap(Function.identity(), v -&gt; true, (a,b) -&gt; false));那么后面的过滤也会简单一点:map.entrySet().stream() .filter(entry -&gt; unique.get(entry.getValue())) .map(Map.Entry::getKey) .collect(Collectors.toList())
    【解决方案2】:

    您可以在创建subList 时过滤频率为1 的值,例如:

    Set<String> uniqueSet = map.values().stream()
            .collect(Collectors.groupingBy(a -> a, Collectors.counting()))
            .entrySet().stream()
            .filter(a -> a.getValue() == 1)
            .map((Map.Entry::getKey))
            .collect(Collectors.toSet());
    

    然后执行相同的操作:

    Set<Integer> result = map.entrySet().stream()
            .filter(e -> uniqueSet.contains(e.getValue()))
            .map(Map.Entry::getKey)
            .collect(Collectors.toSet());
    

    或者正如 Holger 在 cmets 中指出的那样,您可以取消 Boolean 值,而不是 Boolean,以将唯一值过滤为:

    Set<String> uniqueSet = map.values().stream()
            .collect(Collectors.toMap(Function.identity(), v -> true, (a,b) -> false))
            .entrySet().stream()
            .filter(Map.Entry::getValue)
            .map((Map.Entry::getKey))
            .collect(Collectors.toSet());
    

    【讨论】:

    • 类似于this comment,可以将.collect(Collectors.groupingBy(a -&gt; a, Collectors.counting())) .entrySet().stream() .filter(a -&gt; a.getValue() == 1)替换为.collect(Collectors.toMap(Function.identity(), v -&gt; true, (a,b) -&gt; false)) .entrySet().stream() .filter(Map.Entry::getValue)。顺便说一句,subListSet 的一个奇怪名称。
    • @Holger 谢谢。我还记得groupingBy 的另一条评论,将reduce 下游替换为toMap(不确定它是否有助于以某种方式找到问题的替代解决方案)。在命名部分,我肯定需要学习花更多时间思考要编写什么代码,而不仅仅是写下来。
    • 当你有一个像counting() 这样的内置收集器时,它可以帮助减少大型组的装箱开销。如果您事先知道组不会很大(与组的数量相比),减少可能更可取,这同样适用于布尔解决方案,因为装箱开销可以忽略不计。然后,toMapgroupingByreduce 更可取,正如in the Q&A 所讨论的那样。我想,名字的问题是,有时我们从不同的想法开始,在修改代码之后,我们忘记了调整名字......
    猜你喜欢
    • 1970-01-01
    • 2021-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-16
    • 2019-05-17
    • 1970-01-01
    • 2015-02-13
    相关资源
    最近更新 更多