【问题标题】:Intersection of lists with different size不同大小列表的交集
【发布时间】:2021-02-01 09:32:33
【问题描述】:

我有这个面试任务。我认为这很简单,但是...

我有两个列表:

List<String> l1 = List.of("A", "B", "A", "A");
List<String> l2 = List.of("A", "C", "A");

我需要找出这两个列表的共同点。

当然,我试过这个解决方案:

List<String> l1 = List.of("A", "B", "A", "A");
List<String> l2 = List.of("A", "C", "A");
Set<String> l3 = new HashSet<>(l1);
l3.retainAll(l2);
System.out.println(l3); // outputs A

还有这个:

List<String> l1 = List.of("A", "B", "A", "A");
List<String> l2 = List.of("A", "C", "A");
List<String> l3 = new ArrayList<>(l1);
l3.retainAll(l2);
System.out.println(l3); // outputs A, A, A

但我只需要输出A, A

我能做什么?

【问题讨论】:

    标签: java list collections set intersection


    【解决方案1】:

    也许我遗漏了什么,但我认为这就足够了:

    List<String> tmp = new ArrayList<>(l2);
    List<String> result = l1.stream().filter(tmp::remove).collect(Collectors.toList());
    

    运行代码:

    public static void main(String []args){
        List<String> l1 = List.of("A", "B", "A", "A");
        List<String> l2 = List.of("A", "C", "A");
        List<String> tmp = new ArrayList<>(l2);
        List<String> result = l1.stream().filter(tmp::remove).collect(Collectors.toList());
        System.out.println(result);
    }
    

    输出:

    [A, A]
    

    或基于Holger反馈:

    这可能是最简单的,虽然不是最有效的大型 由于重复的线性搜索而导致的列表。此外,有状态的 Stream 操作不鼓励使用谓词;你可以避免他们 通过使用:

    List<String> result = new ArrayList<>(l1); 
    result.removeIf(e-> !tmp.remove(e));
    

    【讨论】:

      【解决方案2】:

      似乎需要先计算输入列表中每个元素的“频率”,然后重建包含频率较低的元素的列表,例如:

      Map<String, Integer> map1 = l1.stream().collect(Collectors.groupingBy(x -> x, Collectors.summingInt(x -> 1)));
      Map<String, Integer> map2 = l2.stream().collect(Collectors.groupingBy(x -> x, Collectors.summingInt(x -> 1)));
      
      List<String> intersection = map1.entrySet().stream()
              .filter(e -> map2.containsKey(e.getKey())) // filter out missing keys
              .flatMap(e -> Collections.nCopies(
                  Math.min(e.getValue(), map2.get(e.getKey())), e.getKey()
                  ).stream()
              )
              .collect(Collectors.toList());
      
      System.out.println(intersection);
      

      更简单的解决方案是使用普通的groupingBy 立即获取子列表并比较子列表的大小以选择较短的:

      Map<String, List<String>> map1 = l1.stream().collect(Collectors.groupingBy(x -> x));
      Map<String, List<String>> map2 = l2.stream().collect(Collectors.groupingBy(x -> x));
      
      List<String> intersection2 = map1.entrySet().stream()
              .flatMap(e ->
                  (e.getValue().size() < map2.computeIfAbsent(
                      e.getKey(), (k) -> Collections.emptyList()                    
                  ).size() ? e.getValue() : map2.get(e.getKey())
              ).stream())
              .collect(Collectors.toList());
      

      使用Stream.filter 不需要修改map2compute/computeIfAbsent

      List<String> intersection3 = map1.entrySet().stream()
              .filter(e -> map2.containsKey(e.getKey()))
              .flatMap(e ->
                      (e.getValue().size() < map2.get(e.getKey()).size() 
                          ? e.getValue() : map2.get(e.getKey()))
                      .stream()
              )
              .collect(Collectors.toList());
      

      【讨论】:

      • map2.get(e.getKey()) 更改为 map2.getOrDefault(e.getKey(), 0) 并且您不需要 filter 步骤。使用单个步骤,.flatMap(e -&gt; Collections.nCopies(Math.min(e.getValue(), map2.getOrDefault(e.getKey(), 0)), e.getKey()).stream()) 而不是map(…).flatMap(…),您不需要构造临时的Map.Entry 实例。你甚至可以将它融合到一个收集操作中,List&lt;String&gt; intersection = map1.entrySet().stream() .collect(ArrayList::new, (e,l) -&gt; l.addAll(Collections.nCopies(Math.min(e.getValue(), map2.getOrDefault(e.getKey(), 0)), e.getKey())), List::addAll);
      • @Holger 我想出了另一个解决方案
      • 删除Collectors.summingInt(x -&gt; 1)意味着现在收集到Lists(分配和填充内存),只是为了在结果上调用size(),然后,compute(…, (k, v) -&gt; null == v ? Collections.emptyList() : v),这是不必要的computeIfAbsent(…, k -&gt; Collections.emptyList()) 的复杂替代方法,意味着修改映射尽管从未使用修改的条目(与 getOrDefault 相反),并且您将简单的 min 替换为手动编写的条件表达式。在我看来,这些变化都不是对您的第一个变体的改进。
      • Collections.nCopies 确实为每个元素分配内存。它创建一个包含单个元素和计数的专用列表。 nCopies(1, …) 占用与 nCopies(1000, …) 相同的内存量。除此之外,您的第一种方法在 Stream 处理期间临时创建它们,一次创建和使用一个,允许对所有以前使用的进行垃圾收集,而 groupingBy 创建的映射引用所有这些,要求它们全部同时存在。
      • 您认为getOrDefault 难以阅读或理解?有趣的。以下是单个收集操作的样子:ideone.com/aXromd
      【解决方案3】:

      您的两个解决方案几乎都是正确的,只是在第一个解决方案中您使用Set#retainAll 而不是List#retainAll,而在第二个解决方案中,已经更接近了,您在相反的方向使用它。你的代码应该是:

      List<String> l1 = List.of("A", "B", "A", "A");
      List<String> l2 = List.of("A", "C", "A");
      List<String> l4 = new ArrayList<>(l2);
      l4.retainAll(l1);
      System.out.println(l4); // [A, A]
      

      【讨论】:

        猜你喜欢
        • 2012-06-01
        • 1970-01-01
        • 2022-06-15
        • 2020-04-25
        • 2020-02-04
        • 1970-01-01
        • 2022-06-15
        • 1970-01-01
        • 2021-08-24
        相关资源
        最近更新 更多