首页 文章

找到两个不同的列表是否包含完全相同的元素的简单方法?

提问于
浏览
202

在标准Java库中,查找两个列表是否包含完全相同的元素的最简单方法是什么?

如果两个列表是相同的实例,则无关紧要,列表的类型参数是否不同也无关紧要 .

例如

List list1
List<String> list2; 
// ... construct etc

list1.add("A");
list2.add("A"); 
// the function, given these two lists, should return true

我知道可能有些东西盯着我:-)


编辑:为了澄清,我正在按顺序寻找完全相同的元素和元素数量 .


编辑:谢谢你指出我看不见的明显答案:-)

虽然到目前为止给出的所有答案都是正确的,但有些答案比其他答案更正确,所以在接受之前我会等待一段时间以获得最佳舍入答案 .

16 回答

  • 0

    汤姆的回答非常好我完全同意他的答案!

    这个问题的一个有趣的方面是,你是否需要 List 类型本身及其固有的顺序 .

    如果不是,您可以降级为 IterableCollection 这使您可以灵活地传递按插入时间排序的数据结构,而不是在您要检查时 .

    如果订单永远不重要(并且您没有重复的元素),请考虑使用 Set .

    如果顺序很重要但是由插入时间定义(并且您没有重复项),请考虑 LinkedHashSet ,它类似于TreeSet但按插入时间排序(不计算重复项) . 这也为您提供了 O(1)O(1) 摊销访问权限 .

  • 75

    如果您关心订单,那么只需使用equals方法:

    list1.equals(list2)
    

    来自javadoc:

    将指定对象与此列表进行比较以获得相等性 . 当且仅当指定的对象也是列表时,返回true,两个列表具有相同的大小,并且两个列表中的所有对应元素对都相等 . (如果(e1 == null?e2 == null:e1.equals(e2)),则两个元素e1和e2相等 . )换句话说,如果两个列表包含相同顺序的相同元素,则它们被定义为相等 . 此定义确保equals方法在List接口的不同实现中正常工作 .

    如果要独立于顺序进行检查,可以将所有元素复制到集合,并在结果集合上使用等于:

    public static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
        return new HashSet<>(list1).equals(new HashSet<>(list2));
    }
    

    这种方法的局限在于它不仅忽略了顺序,而且忽略了重复元素的频率 . 例如,如果 list1 是["A","B","A"]且 list2 是["A","B","B"], Set 方法会认为它们是相等的 .

    如果您需要对订单不敏感但对重复频率敏感,您可以:

  • 1

    我在评论中发布了一些内容,我认为它保证了自己的答案 .

    正如大家在这里所说,使用equals()取决于顺序 . 如果您不关心订单,您有3个选择 .

    Option 1

    使用 containsAll() . 在我看来,这个选项并不理想,因为它提供了最差的性能,O(n ^ 2) .

    Option 2

    这有两个变化:

    2a) 如果您不关心维护列表的顺序...请在两个列表中使用 Collections.sort() . 然后使用 equals() . 这是O(nlogn),因为你做了两种排序,然后是O(n)比较 .

    2b) 如果需要维护列表的顺序,可以先复制两个列表 . 然后,您可以在复制的列表上使用解决方案 2a . 然而,如果复制非常昂贵,这可能没有吸引力 .

    这导致:

    Option 3

    如果您的要求与零件 2b 相同,但复制过于昂贵 . 您可以使用TreeSet为您进行排序 . 将每个列表转储到自己的TreeSet中 . 它将在集合中排序,原始列表将保持不变 . 然后在 TreeSet 上执行 equals() 比较 . TreeSets 可以在O(nlogn)时间内构建, equals() 可以是O(n) .

    拿你的选择:-) .

    EDIT: 我几乎忘记了Laurence Gonsalves指出的同一个警告 . TreeSet实现将消除重复 . 如果您关心重复项,则需要某种排序的多重集 .

  • -2

    如果你正在使用(或者很乐意使用)Apache Commons Collections,你可以使用CollectionUtils.isEqualCollection,其中"returns true iff the given Collections contain exactly the same elements with exactly the same cardinalities."

  • 0

    我知道这是一个旧线程,但其他答案都没有完全解决我的用例(我猜Guava Multiset可能也会这样做,但这里没有例子) . 请原谅我的格式 . 我仍然很想在堆栈交换上发帖 . 另外,如果有任何错误,请告诉我

    假设您有 List<T> a和 List<T> b并且您想要检查它们是否与以下条件相同:

    1)O(n)预期运行时间
    2)等式定义为:对于a或b中的所有元素,元素在a中出现的次数等于元素在b中出现的次数 . 元素相等定义为T.equals()

    private boolean listsAreEquivelent(List<? extends Object> a, List<? extends Object> b) {
        if(a==null) {
            if(b==null) {
                //Here 2 null lists are equivelent. You may want to change this.
                return true;
            } else {
                return false;
            }
        }
        if(b==null) {
            return false;
        }
        Map<Object, Integer> tempMap = new HashMap<>();
        for(Object element : a) {
            Integer currentCount = tempMap.get(element);
            if(currentCount == null) {
                tempMap.put(element, 1);
            } else {
                tempMap.put(element, currentCount+1);
            }
        }
        for(Object element : b) {
            Integer currentCount = tempMap.get(element);
            if(currentCount == null) {
                return false;
            } else {
                tempMap.put(element, currentCount-1);
            }
        }
        for(Integer count : tempMap.values()) {
            if(count != 0) {
                return false;
            }
        }
        return true;
    }
    

    运行时间是O(n)因为我们正在对散列映射进行O(2 * n)插入,并且O(3 * n)散列映射选择 . 我还没有完全测试这段代码,所以要小心:)

    //Returns true:
    listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","A"));
    listsAreEquivelent(null,null);
    //Returns false:
    listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","B"));
    listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("A","B"));
    listsAreEquivelent(Arrays.asList("A","A","B"),null);
    
  • 1

    聚会很晚但想要添加这个空的安全检查:

    Objects.equals(list1, list2)
    
  • 4

    试试这个版本不要求顺序相同,但支持多个相同的值 . 它们只有在每个具有相同数量的任何值时才匹配 .

    public boolean arraysMatch(List<String> elements1, List<String> elements2) {
        // Optional quick test since size must match
        if (elements1.size() != elements2.size()) {
            return false;
        }
        List<String> work = newArrayList(elements2);
        for (String element : elements1) {
            if (!work.remove(element)) {
                return false;
            }
        }
        return work.isEmpty();
    }
    
  • 2

    解决两个列表具有相同元素但顺序不同的情况:

    public boolean isDifferentLists(List<Integer> listOne, List<Integer> listTwo) {
        if(isNullLists(listOne, listTwo)) {
            return false;
        }
    
        if (hasDifferentSize(listOne, listTwo)) {
            return true;
        }
    
        List<Integer> listOneCopy = Lists.newArrayList(listOne);
        List<Integer> listTwoCopy = Lists.newArrayList(listTwo);
        listOneCopy.removeAll(listTwoCopy);
    
        return CollectionUtils.isNotEmpty(listOneCopy);
    }
    
    private boolean isNullLists(List<Integer> listOne, List<Integer> listTwo) {
        return listOne == null && listTwo == null;
    }
    
    private boolean hasDifferentSize(List<Integer> listOne, List<Integer> listTwo) {
        return (listOne == null && listTwo != null) || (listOne != null && listTwo == null) || (listOne.size() != listTwo.size());
    }
    
  • 0

    List上的equals方法将执行此操作,列表是有序的,因此要等于两个列表必须具有相同顺序的相同元素 .

    return list1.equals(list2);
    
  • 1

    示例代码:

    public static '<'T'>' boolean isListDifferent(List'<'T'>' previousList,
            List'<'T'>' newList) {
    
        int sizePrevoisList = -1;
        int sizeNewList = -1;
    
        if (previousList != null && !previousList.isEmpty()) {
            sizePrevoisList = previousList.size();
        }
        if (newList != null && !newList.isEmpty()) {
            sizeNewList = newList.size();
        }
    
        if ((sizePrevoisList == -1) && (sizeNewList == -1)) {
            return false;
        }
    
        if (sizeNewList != sizePrevoisList) {
            return true;
        }
    
        List n_prevois = new ArrayList(previousList);
        List n_new = new ArrayList(newList);
    
        try {
            Collections.sort(n_prevois);
            Collections.sort(n_new);
        } catch (ClassCastException exp) {
            return true;
        }
    
        for (int i = 0; i < sizeNewList; i++) {
            Object obj_prevois = n_prevois.get(i);
            Object obj_new = n_new.get(i);
            if (obj_new.equals(obj_prevois)) {
                // Object are same
            } else {
                return true;
            }
        }
    
        return false;
    }
    
  • 6

    除了劳伦斯的答案,如果你还想让它安全无效:

    private static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
        if (list1 == null)
            return list2==null;
        if (list2 == null)
            return list1 == null;
        return new HashSet<>(list1).equals(new HashSet<>(list2));
    }
    
  • 1
    list1.equals(list2);
    

    如果列表包含自定义类MyClass,则此类必须覆盖 equals 函数 .

    class MyClass
      {
      int field=0;
      @0verride
      public boolean equals(Object other)
            {
            if(this==other) return true;
            if(other==null || !(other instanceof MyClass)) return false;
            return this.field== MyClass.class.cast(other).field;
            }
      }
    

    注意:如果要在java.util.Set而不是 java.util.List 上测试equals,那么您的对象必须覆盖 hashCode 函数 .

  • 5
  • 2

    您可以使用Apache的org.apache.commons.collections库:http://commons.apache.org/collections/apidocs/org/apache/commons/collections/ListUtils.html

    public static boolean isEqualList(java.util.Collection list1,
                                  java.util.Collection list2)
    
  • 10

    检查两个列表都不为空 . 如果它们的大小不同,那么这些列表就不相同了 . 构建包含列表元素作为键及其重复值的映射,并比较映射 .

    假设,如果两个列表都为空,我认为它们是相等的 .

    private boolean compareLists(List<?> l1, List<?> l2) {
        if (l1 == null && l2 == null) {
            return true;
        } else if (l1 == null || l2 == null) {
            return false;
        }
    
        if (l1.size() != l2.size()) {
            return false;
        }
    
        Map<?, Integer> m1 = toMap(l1);
        Map<?, Integer> m2 = toMap(l2);
    
        return m1.equals(m2);
    }
    
    private Map<Object, Integer> toMap(List<?> list) {
        //Effective size, not to resize in the future.
        int mapSize = (int) (list.size() / 0.75 + 1);
        Map<Object, Integer> map = new HashMap<>(mapSize);
    
        for (Object o : list) {
            Integer count = map.get(o);
            if (count == null) {
                map.put(o, 1);
            } else {
                map.put(o, ++count);
            }
        }
    
        System.out.println(map);
        return map;
    }
    

    请注意,应该为这些对象正确定义方法等于 . https://stackoverflow.com/a/24814634/4587961

  • 287

    这取决于您使用的具体List类 . 抽象类AbstractCollection有一个名为containsAll(Collection)的方法,它接受另一个集合(List是一个集合)和:

    如果此collection包含指定collection中的所有元素,则返回true .

    因此,如果传入ArrayList,您可以调用此方法以查看它们是否完全相同 .

    List foo = new ArrayList();
        List bar = new ArrayList();
        String str = "foobar";
    
        foo.add(str);
        bar.add(str);
    
        foo.containsAll(bar);
    

    containsAll()的原因是因为它遍历第一个列表,在第二个列表中查找匹配项 . 因此,如果它们发生故障,则equals()将不会接收它 .

    编辑:我只想在这里评论一下执行所提供的各种选项的摊销运行时间 . 运行时间重要吗?当然 . 这是你唯一应该考虑的事情吗?没有 .

    将每个单个元素从列表复制到其他列表的成本需要花费时间,并且它还占用了大量内存(有效地使您使用的内存加倍) .

    因此,如果JVM中的内存不是问题(通常应该是这样),那么您仍然需要考虑将两个列表中的每个元素复制到两个TreeSet中所花费的时间 . 请记住,它会在每个元素进入时对其进行排序 .

    我最后的建议?在此处做出正确决策之前,您需要考虑数据集以及数据集中的元素数量,以及数据集中每个对象的大小 . 与他们一起玩,每个方向创建一个,看看哪一个运行得更快 . 这是一个很好的锻炼 .

相关问题