Java中ArrayList的交集和并集

Posted

技术标签:

【中文标题】Java中ArrayList的交集和并集【英文标题】:Intersection and union of ArrayLists in Java 【发布时间】:2011-07-14 01:45:48 【问题描述】:

有什么方法可以做到吗?我正在寻找但找不到任何东西。

另一个问题:我需要这些方法来过滤文件。 有些是AND 过滤器,有些是OR 过滤器(就像在集合论中一样),所以我需要根据所有文件和包含这些文件的unite/intersects ArrayLists 进行过滤。

我应该使用不同的数据结构来保存文件吗?还有什么可以提供更好的运行时吗?

【问题讨论】:

如果您不想创建新列表,Vector.retainAll(Vector) 会将您的原始向量修剪为仅与第二个向量的交集。 @user2808054 为什么是Vector?从 Java 1.2 开始不鼓励使用该类。 @dimo414 我正在使用的接口(我没有选择)将事物作为向量返回。我不知道它已经气馁了!感谢您的信息.. 被谁气馁?我没有看到任何关于它被弃用的说明,所以这是一个惊喜 来自 Javadocs:“As of the Java 2 platform v1.2 ... it is recommended to use ArrayList in place of Vector.”。您可能需要Vector的唯一一次是用于跨线程交互,但对于这些用例也有更安全的数据结构。另见this question。在我看来,任何在 2016 年仍在使用 Vector 的图书馆都非常可疑。 @dimo414 这是一个 IBM 库,哈哈! (Lotus Domino 数据 API)。感谢您的信息,非常有帮助 【参考方案1】:

您可以从apache commons 使用CollectionUtils

【讨论】:

如果有人觉得这个答案有点太短:'CollectionUtils.containsAny' 和 'CollectionUtils.containsAll' 是方法。 很奇怪,来自 apache commons 的 CollectionUtils 不支持泛型【参考方案2】:
list1.retainAll(list2) - is intersection

联合将是removeAll,然后是addAll

在集合的文档中找到更多(ArrayList 是一个集合) http://download.oracle.com/javase/1.5.0/docs/api/java/util/Collection.html

【讨论】:

retainAll()removeAll() 都是对列表的 O(n^2) 操作。我们可以做得更好。 我投了赞成票,但现在我有一个问题。 retainAll 的 1, 2, 2, 3, 4, 5 超过 1, 2, 3 导致 1, 2, 2, 3。交点不应该是1, 2, 3吗? @ghchoi list 和 set 背后的语义现在是问题所在。使用列表 [1, 2, 2, 3, 4, 5] 我们接受重复但对于集合 1, 2, 3 不允许重复。另外两种表示法通常不同但不固定,对于列表 [...重复是一个特征...] 和对于集合 ...不允许重复...【参考方案3】:

Collection(所以 ArrayList 也是)有:

col.retainAll(otherCol) // for intersection
col.addAll(otherCol) // for union

如果接受重复则使用 List 实现,如果不接受则使用 Set 实现:

Collection<String> col1 = new ArrayList<String>(); // a, b, c
// Collection<String> col1 = new TreeSet<String>();
col1.add("a");
col1.add("b");
col1.add("c");

Collection<String> col2 = new ArrayList<String>(); // b, c, d, e
// Collection<String> col2 = new TreeSet<String>();
col2.add("b");
col2.add("c");
col2.add("d");
col2.add("e");

col1.addAll(col2);
System.out.println(col1); 
//output for ArrayList: [a, b, c, b, c, d, e]
//output for TreeSet: [a, b, c, d, e]

【讨论】:

有人建议编辑此联合“不正确,因为它将包含两次公共元素”。编辑建议改用HashSet 其实已经编辑过了,见:“如果接受重复就用List实现,不接受就用Set实现:” 不,retainAll 不是列表的交集。在上面,col 中所有不在 otherCol 中的元素都被删除。假设 otherCol 是 a,b,b,c 而 col 是 b,b,b,c,d。然后 col 以 b,b,b,c 结束,这不是严格意义上的两者的交集。我希望那是b,b,c。正在执行不同的操作。 我也看不出addAll() 是列表的联合;它只是将第二个列表连接到第一个列表的末尾。如果第一个列表已包含元素,则联合操作将避免添加元素。【参考方案4】:

如果您将数据放在 Sets 中,则可以使用 Guava 的 Sets 类。

【讨论】:

【参考方案5】:

如果你想对它们进行交集和联合,我认为你应该使用Set 来保存文件。然后您可以使用Guava 的Sets 类来执行unionintersection 并通过Predicate 进行过滤。这些方法与其他建议的区别在于,所有这些方法都创建了两个集合的并集、交集等的惰性视图。 Apache Commons 创建一个新集合并将数据复制到其中。 retainAll 通过从中删除元素来更改您的集合之一。

【讨论】:

【参考方案6】:

仅为集合而非列表定义的联合和交集。正如你所提到的。

检查guava 库中的过滤器。番石榴也提供真实的intersections and unions

 static <E> Sets.SetView<E >union(Set<? extends E> set1, Set<? extends E> set2)
 static <E> Sets.SetView<E> intersection(Set<E> set1, Set<?> set2)

【讨论】:

【参考方案7】:

这是一个没有使用任何第三方库的简单实现。与retainAllremoveAlladdAll 相比,主要优势在于这些方法不会修改输入到方法的原始列表。

public class Test 

    public static void main(String... args) throws Exception 

        List<String> list1 = new ArrayList<String>(Arrays.asList("A", "B", "C"));
        List<String> list2 = new ArrayList<String>(Arrays.asList("B", "C", "D", "E", "F"));

        System.out.println(new Test().intersection(list1, list2));
        System.out.println(new Test().union(list1, list2));
    

    public <T> List<T> union(List<T> list1, List<T> list2) 
        Set<T> set = new HashSet<T>();

        set.addAll(list1);
        set.addAll(list2);

        return new ArrayList<T>(set);
    

    public <T> List<T> intersection(List<T> list1, List<T> list2) 
        List<T> list = new ArrayList<T>();

        for (T t : list1) 
            if(list2.contains(t)) 
                list.add(t);
            
        

        return list;
    

【讨论】:

你可以用list1元素创建新列表,然后调用retainAll、addAll方法 为什么在这个解决方案中使用 strictfp? 应该为intersection 使用HashSet,这样平均情况下的性能是O(n) 而不是O(n^2)。 这篇文章可以使用更新来展示 Java 8 Stream API 的好处。 当我尝试分配此值时出现错误 -> 示例:ArrayList total total = (ArrayList) intersection(list2, list1) --->无法转换 java.util。 arraylist 到 java.util.arraylist【参考方案8】:

标记的解决方案无效。它的时间复杂度为 O(n^2)。我们可以做的是对两个列表进行排序,并执行如下所示的交集算法。

private  static ArrayList<Integer> interesect(ArrayList<Integer> f, ArrayList<Integer> s)  
    ArrayList<Integer> res = new ArrayList<Integer>();

    int i = 0, j = 0; 
    while (i != f.size() && j != s.size())  

        if (f.get(i) < s.get(j)) 
            i ++;
         else if (f.get(i) > s.get(j))  
            j ++;
         else  
            res.add(f.get(i)); 
            i ++;  j ++;
        
    


    return res; 

这个复杂度为 O(n log n + n),在 O(n log n) 中。 联合以类似的方式完成。只需确保对 if-elseif-else 语句进行适当的修改。

如果需要,您也可以使用迭代器(我知道它们在 C++ 中更有效,我不知道这在 Java 中是否也是如此)。

【讨论】:

不够通用,T 可能无法比较,在某些情况下比较成本很高...... 不是通用的,我完全同意。比较贵?你会怎么解决呢? 可悲的是 - 在 O(n^2) 中这样做会更便宜 :) 对于数字,这个解决方案很好...... 很遗憾 - 你没有回答我的问题。让我换个说法,给定成本 c(n) 的比较函数,O(n^2) 如何更好? 将一个输入转换为一组并在循环中调用contains()(正如 Devenv 所建议的那样)将花费 O(n + m) 时间。排序是不必要的复杂,需要 O(n log n + m log n + n) 时间。当然,这会减少到 O(n log n) 时间,但这仍然比线性时间差,而且要复杂得多。【参考方案9】:

这是一种与流进行交集的方法(请记住,您必须使用 java 8 处理流):

List<foo> fooList1 = new ArrayList<>(Arrays.asList(new foo(), new foo()));
List<foo> fooList2 = new ArrayList<>(Arrays.asList(new foo(), new foo()));
fooList1.stream().filter(f -> fooList2.contains(f)).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());

【讨论】:

【参考方案10】: retainAll 将修改您的列表 Guava 没有 List 的 API(仅用于 set)

我发现 ListUtils 对于这个用例非常有用。

如果您不想修改现有列表,请使用 org.apache.commons.collections 中的 ListUtils。

ListUtils.intersection(list1, list2)

【讨论】:

【参考方案11】:

这篇文章相当老了,但它是第一个在谷歌上搜索该主题时弹出的帖子。

我想在一行中使用 Java 8 流(基本上)做同样的事情来提供更新:

List<T> intersect = list1.stream()
    .filter(list2::contains)
    .collect(Collectors.toList());

List<T> union = Stream.concat(list1.stream(), list2.stream())
    .distinct()
    .collect(Collectors.toList());

如果有人有更好/更快的解决方案,请告诉我,但这个解决方案是一个很好的单行程序,可以很容易地包含在方法中,而无需添加不必要的帮助类/方法,并且仍然保持可读性。

【讨论】:

哎呀,这可能是一个不错的单行,但它需要 O(n^2) 时间。将其中一个列表转换为Set,然后使用集合的contains 方法。并非生活中的所有事情都必须通过流来完成。【参考方案12】:

如果列表中的对象是可散列的(即有一个不错的 hashCode 和 equals 函数),表之间最快的方法大约是。 size > 20 是为两个列表中较大的一个构造一个 HashSet。

public static <T> ArrayList<T> intersection(Collection<T> a, Collection<T> b) 
    if (b.size() > a.size()) 
        return intersection(b, a);
     else 
        if (b.size() > 20 && !(a instanceof HashSet)) 
            a = new HashSet(a);
        
        ArrayList<T> result = new ArrayList();
        for (T objb : b) 
            if (a.contains(objb)) 
                result.add(objb);
            
        
        return result;
    

【讨论】:

【参考方案13】:

最终解决方案:

//all sorted items from both
public <T> List<T> getListReunion(List<T> list1, List<T> list2) 
    Set<T> set = new HashSet<T>();
    set.addAll(list1);
    set.addAll(list2);
    return new ArrayList<T>(set);


//common items from both
public <T> List<T> getListIntersection(List<T> list1, List<T> list2) 
    list1.retainAll(list2);
    return list1;


//common items from list1 not present in list2
public <T> List<T> getListDifference(List<T> list1, List<T> list2) 
    list1.removeAll(list2);
    return list1;

【讨论】:

第一种方法不变异,后两种变异,感觉不一致。将list1.retainAll(list2); 包裹在getListIntersection 中有什么意义?它已经是一行,它可能会隐藏它正在改变第一个列表,因为它返回一个列表【参考方案14】:

我也在处理类似的情况,并来到这里寻求帮助。最终找到了我自己的数组解决方案。 ArrayList AbsentDates = new ArrayList(); // 将存储 Array1-Array2

注意:如果它可以帮助某人访问此页面寻求帮助,请发布此内容。

ArrayList<String> AbsentDates = new ArrayList<String>();//This Array will store difference
      public void AbsentDays() 
            findDates("April", "2017");//Array one with dates in Month April 2017
            findPresentDays();//Array two carrying some dates which are subset of Dates in Month April 2017

            for (int i = 0; i < Dates.size(); i++) 

                for (int j = 0; j < PresentDates.size(); j++) 

                    if (Dates.get(i).equals(PresentDates.get(j))) 

                        Dates.remove(i);
                                   

                              
                AbsentDates = Dates;   
            
            System.out.println(AbsentDates );
        

【讨论】:

【参考方案15】:

如果数字匹配而不是我检查它是否第一次出现,如果数字第一次匹配,则在“indexOf()”的帮助下打印并保存到字符串中,这样当下一次相同的数字匹配时它不会打印,因为由于“indexOf()”条件将是错误的。

class Intersection

public static void main(String[] args)
 
  String s="";
    int[] array1 = 1, 2, 5, 5, 8, 9, 7,2,3512451,4,4,5 ,10;
    int[] array2 = 1, 0, 6, 15, 6, 5,4, 1,7, 0,5,4,5,2,3,8,5,3512451;


       for (int i = 0; i < array1.length; i++)
       
           for (int j = 0; j < array2.length; j++)
           
               char c=(char)(array1[i]);
               if(array1[i] == (array2[j])&&s.indexOf(c)==-1)
                   
                System.out.println("Common element is : "+(array1[i]));
                s+=c;
                
           
           

【讨论】:

不要只是发布代码作为答案,对你在做什么做一些简单的解释 这是我上传的第一个程序 虽然这段代码可能有助于解决问题,但它并没有解释为什么和/或如何回答问题。提供这种额外的背景将显着提高其长期价值。请edit您的答案添加解释,包括适用的限制和假设。【参考方案16】:

首先,我将数组的所有值复制到一个数组中,然后将重复值删除到数组中。第 12 行,解释如果相同的数字出现的时间超过时间,则将一些额外的垃圾值放入“j”位置。最后从start-end遍历,检查是否有相同的垃圾值,然后丢弃。

public class Union 
public static void main(String[] args)

    int arr1[]=1,3,3,2,4,2,3,3,5,2,1,99;
    int arr2[]=1,3,2,1,3,2,4,6,3,4;
    int arr3[]=new int[arr1.length+arr2.length];

    for(int i=0;i<arr1.length;i++)
        arr3[i]=arr1[i];

    for(int i=0;i<arr2.length;i++)
        arr3[arr1.length+i]=arr2[i];
    System.out.println(Arrays.toString(arr3));

    for(int i=0;i<arr3.length;i++)
    
        for(int j=i+1;j<arr3.length;j++)
        
            if(arr3[i]==arr3[j])
                arr3[j]=99999999;          //line  12
        
    
    for(int i=0;i<arr3.length;i++)
    
        if(arr3[i]!=99999999)
            System.out.print(arr3[i]+" ");
    
   

【讨论】:

欢迎来到 Stack Overflow!请注意,问题是关于 ArrayList。另外,我担心这种特殊的实现会留下一些不足之处。用作标记的值 99999999 可能出现在输入中。最好使用动态结构,比如ArrayList,来存储联合的结果。 请解释您提供的代码,而不仅仅是代码答案。 我只是给你一个线索,你必须把任何垃圾值 很高兴看到您添加了解释。不幸的是,答案本身仍然很糟糕。没有理由使用数组。您应该使用像 ArrayList 这样的动态结构。如果(出于某种原因)您必须使用数组,您应该考虑使用Integer 的数组而不是int。然后你可以使用null 而不是你的“垃圾值”。 “垃圾值”或“标记值”通常不是一个好主意,因为这些值可能仍会出现在输入中。【参考方案17】:

在 Java 8 中,我使用如下简单的辅助方法:

public static <T> Collection<T> getIntersection(Collection<T> coll1, Collection<T> coll2)
    return Stream.concat(coll1.stream(), coll2.stream())
            .filter(coll1::contains)
            .filter(coll2::contains)
            .collect(Collectors.toSet());


public static <T> Collection<T> getMinus(Collection<T> coll1, Collection<T> coll2)
    return coll1.stream().filter(not(coll2::contains)).collect(Collectors.toSet());


public static <T> Predicate<T> not(Predicate<T> t) 
    return t.negate();

【讨论】:

【参考方案18】:

您可以使用 commons-collections4 CollectionUtils

Collection<Integer> collection1 = Arrays.asList(1, 2, 4, 5, 7, 8);
Collection<Integer> collection2 = Arrays.asList(2, 3, 4, 6, 8);

Collection<Integer> intersection = CollectionUtils.intersection(collection1, collection2);
System.out.println(intersection); // [2, 4, 8]

Collection<Integer> union = CollectionUtils.union(collection1, collection2);
System.out.println(union); // [1, 2, 3, 4, 5, 6, 7, 8]

Collection<Integer> subtract = CollectionUtils.subtract(collection1, collection2);
System.out.println(subtract); // [1, 5, 7]

【讨论】:

【参考方案19】:

经过测试,这是我最好的交叉路口方法。

与纯 HashSet 方法相比,速度更快。下面的 HashSet 和 HashMap 对于超过 100 万条记录的数组具有相似的性能。

对于 Java 8 Stream 方法,对于大于 10k 的数组大小,速度非常慢。

希望这能有所帮助。

public static List<String> hashMapIntersection(List<String> target, List<String> support) 
    List<String> r = new ArrayList<String>();
    Map<String, Integer> map = new HashMap<String, Integer>();
    for (String s : support) 
        map.put(s, 0);
    
    for (String s : target) 
        if (map.containsKey(s)) 
            r.add(s);
        
    
    return r;

public static List<String> hashSetIntersection(List<String> a, List<String> b) 
    Long start = System.currentTimeMillis();

    List<String> r = new ArrayList<String>();
    Set<String> set = new HashSet<String>(b);

    for (String s : a) 
        if (set.contains(s)) 
            r.add(s);
        
    
    print("intersection:" + r.size() + "-" + String.valueOf(System.currentTimeMillis() - start));
    return r;


public static void union(List<String> a, List<String> b) 
    Long start = System.currentTimeMillis();
    Set<String> r= new HashSet<String>(a);
    r.addAll(b);
    print("union:" + r.size() + "-" + String.valueOf(System.currentTimeMillis() - start));

【讨论】:

【参考方案20】:

基于公共键的不同对象的两个列表的交集 - Java 8

 private List<User> intersection(List<User> users, List<OtherUser> list) 

        return list.stream()
                .flatMap(OtherUser -> users.stream()
                        .filter(user -> user.getId()
                                .equalsIgnoreCase(OtherUser.getId())))
                .collect(Collectors.toList());
    

【讨论】:

这两个列表之间的差异如何?【参考方案21】:
public static <T> Set<T> intersectCollections(Collection<T> col1, Collection<T> col2) 
    Set<T> set1, set2;
    if (col1 instanceof Set) 
        set1 = (Set) col1;
     else 
        set1 = new HashSet<>(col1);
    

    if (col2 instanceof Set) 
        set2 = (Set) col2;
     else 
        set2 = new HashSet<>(col2);
    

    Set<T> intersection = new HashSet<>(Math.min(set1.size(), set2.size()));

    for (T t : set1) 
        if (set2.contains(t)) 
            intersection.add(t);
        
    

    return intersection;

JDK8+(可能是最佳性能)

public static <T> Set<T> intersectCollections(Collection<T> col1, Collection<T> col2) 
    boolean isCol1Larger = col1.size() > col2.size();
    Set<T> largerSet;
    Collection<T> smallerCol;

    if (isCol1Larger) 
        if (col1 instanceof Set) 
            largerSet = (Set<T>) col1;
         else 
            largerSet = new HashSet<>(col1);
        
        smallerCol = col2;
     else 
        if (col2 instanceof Set) 
            largerSet = (Set<T>) col2;
         else 
            largerSet = new HashSet<>(col2);
        
        smallerCol = col1;
    

    return smallerCol.stream()
            .filter(largerSet::contains)
            .collect(Collectors.toSet());

如果您不关心性能并喜欢更小的代码,请使用:

col1.stream().filter(col2::contains).collect(Collectors.toList());

【讨论】:

【参考方案22】:

retainAll() 方法用于查找公共元素..即;交集 list1.retainAll(list2)

【讨论】:

【参考方案23】:

自 Java 8 以来的单行代码

导入静态 java.util.stream.Stream.concat;导入静态 java.util.stream.Collectors.toList;导入静态 java.util.stream.Collectors.toSet;

如果没有重复则联合:

  return concat(a.stream(), b.stream()).collect(toList());

联合和不同:

  return concat(a.stream(), b.stream()).distinct().collect(toList());

如果 Collection/Set 返回类型,则联合和不同:

  return concat(a.stream(), b.stream()).collect(toSet());

如果没有重复则相交:

  return a.stream().filter(b::contains).collect(toList());

如果集合 b 很大而不是 O(1),则通过在 return 之前添加 1 行来预优化过滤器性能。复制到HasSet(import java.util.Set;)

... b = Set.copyOf(b);

相交和不同:

  return a.stream().distinct().filter(b::contains).collect(toList());

【讨论】:

【参考方案24】:

您可以使用以下方法:

CollectionUtils.containsAnyCollectionUtils.containsAll

来自Apache Commons。

【讨论】:

以上是关于Java中ArrayList的交集和并集的主要内容,如果未能解决你的问题,请参考以下文章

如何计算 Hive 中两个数组的交集和并集?

java求交集和并集

java集合求差值和并集!

java数组的交集和并集

excel怎么做交集和并集?

PHP数组的几个操作,求并集,交集,差集,数组