【问题标题】:Efficient intersection of two List<String> in Java?Java中两个List<String>的有效交集?
【发布时间】:2011-01-24 22:38:02
【问题描述】:

问题很简单:

我有两个列表

List<String> columnsOld = DBUtils.GetColumns(db, TableName);
List<String> columnsNew = DBUtils.GetColumns(db, TableName);

我需要得到这些的交集。有没有快速的方法来实现这一点?

【问题讨论】:

  • @JohnnyCoder 认真的吗?
  • @Ungeheuer 如果您希望仅在两个列表中都包含重复项时才包含重复项,则该方法不起作用

标签: java list intersection


【解决方案1】:

你可以使用retainAll方法:

columnsOld.retainAll (columnsNew);

【讨论】:

  • 注意:要让它与String以外的其他对象一起工作,你当然需要实现equalshashCode
  • 代码很简单,但算法复杂度很差:O(n×m),而 set version 的复杂度为 O(n+m)。拥有两百万个项目列表,这就是 万亿 次操作和 数百万 次操作之间的区别。
【解决方案2】:

使用 Google 的 Guava 库:

Sets.intersection(Sets.newHashSet(setA), Sets.newHashSet(setB))

注意:这比简单地用两个列表进行交集要高效得多:它是 O(n+m),而 list version 是 O(n×m)。拥有两百万个项目列表,这就是 百万 次操作和 万亿 次操作之间的差异。

【讨论】:

    【解决方案3】:

    由于 retainAll 不会触及参数集合,这会更快:

    List<String> columnsOld = DBUtils.GetColumns(db, TableName); 
    List<String> columnsNew = DBUtils.GetColumns(db, TableName); 
    
    for(int i = columnsNew.size() - 1; i > -1; --i){
        String str = columnsNew.get(i);
        if(!columnsOld.remove(str))
            columnsNew.remove(str);
    }
    

    交集将是 columnsNew 中留下的值。从 columnsOld 中删除已经比较的值将减少所需的比较次数。

    【讨论】:

    • 但是您的代码肯定应该被提取到一个新的单独方法中,因为从这段代码中绝对不清楚它是做什么的。而且我也不会拒绝对此代码进行额外的单元测试。
    • 同意,良好的方法分离、命名和单元测试始终是第一原则。
    • 这个方法不应该把columnsOld中找不到的元素添加到columnsNew中吗?结果中似乎缺少这些元素。
    • 从 columnsOld 中删除列的优化实际上可能没有任何区别(删除本身是有代价的),甚至在像 ArrayList 这样的删除移动元素的情况下更慢。
    【解决方案4】:

    怎么样

    private List<String> intersect(List<String> A, List<String> B) {
        List<String> rtnList = new LinkedList<>();
        for(String dto : A) {
            if(B.contains(dto)) {
                rtnList.add(dto);
            }
        }
        return rtnList;
    }
    

    【讨论】:

    • 如果 B 包含 A 中不包含的元素,则无需遍历这些元素,因为我们正在尝试查找 A 和 B 中的所有元素。
    【解决方案5】:

    流有一种很好的方法,可以在一行代码中执行此操作,并且您可以创建两个不同类型的列表,这在 containsAll 方法 afaik 中是不可能的:

    columnsOld.stream().filter(c -> columnsNew.contains(c)).collect(Collectors.toList());
    

    不同类型列表的示例。如果您在 foo 和 bar 之间有一个实体,并且您可以从 foo 获得一个 bar-object,那么您可以修改您的流:

    List<foo> fooList = new ArrayList<>(Arrays.asList(new foo(), new foo()));
    List<bar> barList = new ArrayList<>(Arrays.asList(new bar(), new bar()));
    
    fooList.stream().filter(f -> barList.contains(f.getBar()).collect(Collectors.toList());
    

    【讨论】:

    • c -&gt; columnsNew.contains(c) lambda 可以重写为更简洁的方法参考:columnsNew::contains
    • 这不会在 O(n^2) 时间内运行吗?
    【解决方案6】:

    如果不关心出现则使用retainAll,否则使用N.intersection

    a = N.asList(12, 16, 16, 17, 19);
    b = N.asList(16, 19, 107);
    a.retainAll(b); // [16, 16, 19]
    N.println(a);
    
    a = N.asList(12, 16, 16, 17, 19);
    b = N.asList(16, 19, 107);
    a = N.intersect(a, b);
    N.println(a); // [16, 19]
    

    N 是abacus-common 中的实用程序类

    【讨论】:

      【解决方案7】:

      如果您将第二个列表放入一个集合中,请说 HashSet。只需遍历第一个列表,检查集合中是否存在,如果不存在则删除,您的第一个列表最终将具有您需要的交集。 这将比 retainAll 或包含在列表中要快得多。 这里的重点是使用集合而不是列表。查找是 O(1)。 firstList.retainAll(new HashSet (secondList)) 也可以。

      【讨论】:

        【解决方案8】:

        使用 org.apache.commons.collections4.ListUtils#intersection

        【讨论】:

          猜你喜欢
          • 2011-11-26
          • 1970-01-01
          • 2014-09-15
          • 1970-01-01
          • 1970-01-01
          • 2011-01-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多