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

时间:2009-07-02 17:24:00

标签: java collections

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

两个列表是否是同一个实例并不重要,如果列表的类型参数不同则无关紧要。

e.g。

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

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

我知道的脸上可能有什么东西让我盯着: - )


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

19 个答案:

答案 0 :(得分:309)

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

list1.equals(list2)

来自javadoc:

  

比较指定的对象   这个平等的清单。返回true   当且仅当指定的对象是   也是一个列表,两个列表都相同   大小,以及所有相应的对   两个列表中的元素是相等的。   (如果,两个元素e1和e2相等   (e1 == null?e2 == null:   e1.equals(e2))。)换句话说,两个   如果列表被定义为相等   包含相同的元素   订购。这个定义确保了   equals方法正常工作   跨越不同的实现   列表界面。

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

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 :(得分:82)

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

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

选项1

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

选项2

这有两种变体:

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

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

这导致:

选项3

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

选择: - )。

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

答案 2 :(得分:16)

如果你正在使用(或者很乐意使用)Apache Commons Collections,你可以使用CollectionUtils.isEqualCollection,如果给定的集合包含完全相同的基数,则返回true。“

答案 3 :(得分:8)

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

Objects.equals(list1, list2)

答案 4 :(得分:6)

我知道这是一个旧线程,但其他答案都没有完全解决我的用例(我猜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);

答案 5 :(得分: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();
}

答案 6 :(得分:2)

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

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

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

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

如果订单很重要但是由插入时间定义(并且您没有重复项),请考虑LinkedHashSet,它类似于TreeSet,但按插入时间排序(不计算重复项)。这也为您O(1)提供了O(log n)分期付款访问权限。

答案 7 :(得分: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());
}

答案 8 :(得分:1)

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

return list1.equals(list2);

答案 9 :(得分:1)

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

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));
}

答案 10 :(得分: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;
}

答案 11 :(得分: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上测试等于,那么您的对象必须覆盖hashCode函数。

答案 12 :(得分:0)

您可以使用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)

答案 13 :(得分:0)

检查两个列表都不为空。如果它们的大小不同,则这些列表不相等。构建由列表组成的地图&#39;元素作为键及其重复值作为值并比较地图。

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

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

答案 14 :(得分:0)

答案 15 :(得分:0)

我的解决方案适用于您不关心 Lists 内的排序的情况 - 换句话说:Lists 具有相同的元素但不同的排序将被视为具有相同的内容.

示例:["word1", "word2"]["word2", "word1"] 被认为具有相同的内容。

我已经解决了订购问题,我还需要说一下重复项。 Lists 需要具有相同数量的元素才能被视为相等。

示例:["word1"]["word1", "word1"] 被认为具有不同的内容。

我的解决方案:

public class ListUtil {

    public static <T> boolean hasSameContents(List<T> firstList, List<T> secondList) {      
        if (firstList == secondList) { // same object
            return true;
        }
        if (firstList != null && secondList != null) {
            if (firstList.isEmpty() && secondList.isEmpty()) {
                return true;
            }
            if (firstList.size() != secondList.size()) {
                return false;
            }
            List<T> tmpSecondList = new ArrayList<>(secondList);
            Object currFirstObject = null;
            for (int i=1 ; i<=firstList.size() ; i++) {
                currFirstObject = firstList.get(i-1);
                boolean removed = tmpSecondList.remove(currFirstObject);
                if (!removed) {
                    return false;
                }
                if (i != firstList.size()) { // Not the last element
                    if (tmpSecondList.isEmpty()) {
                        return false;
                    }
                }
            }
            if (tmpSecondList.isEmpty()) {
                return true;
            }
        }
        return false;
    }
}

我已经使用 Strings 对其进行了如下测试:

<块引用>
@Test
public void testHasSameContents() throws Exception {
    // comparing with same list => no duplicate elements
    Assert.isTrue(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("one", "two", "three")));
    // comparing with same list => duplicate elements
    Assert.isTrue(ListUtil.hasSameContents(List.of("one", "two", "three", "one"), List.of("one", "two", "three", "one")));
    // compare with disordered list => no duplicate elements
    Assert.isTrue(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("three", "two", "one")));
    // compare with disordered list => duplicate elements
    Assert.isTrue(ListUtil.hasSameContents(List.of("one", "two", "three", "one"), List.of("three", "two", "one", "one")));
    // comparing with different list => same size, no duplicate elements
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("four", "five", "six")));
    // comparing with different list => same size, duplicate elements
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "two"), List.of("one", "two", "three")));
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("one", "two", "two")));
    // comparing with different list => different size, no duplicate elements
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three", "four"), List.of("one", "two", "three")));
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("one", "two", "three", "four")));
    // comparing with different list => different sizes, duplicate elements
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three", "one"), List.of("one", "two", "three")));
    Assert.isFalse(ListUtil.hasSameContents(List.of("one", "two", "three"), List.of("one", "two", "three", "one")));
}

答案 16 :(得分:0)

我知道这可能会非常晚,但我个人使用此功能。 如果有人想做一些基准测试,那就太好了。

public static<X> boolean areEqual(List<X> a, List<X> b, BiPredicate<X, X> AEqualsB) {
        boolean aIsNull = a == null;
        boolean bIsNull = b == null;
        if (aIsNull || bIsNull) {
            return aIsNull == bIsNull;
        }
        int size = a.size();
        boolean sameSize = size == b.size();
        if (!sameSize) {return false;} else {
            for (int i = 0; i < size; i++) {
                X aX = a.get(i), bX = b.get(i);
                boolean areEqual = AEqualsB.test(aX, bX);
                if (!areEqual) {
                    return false;
                }
            }
            return true;
        }
    }

顺便说一句,我知道前 5 行可以用 XOR "^" 加一个 else 来简化,但不管你信不信,我很难得到正确的 XOR。

我猜它的效率将取决于谓词的类型,但同时它允许您检查自定义的潜在等式,而忽略对编码人员来说可能无关紧要的差异。

这是代码示例。

ListUtils.areEqual(newElements, oldElements, Element::areEqual)

public boolean areEqual(Element e) {
        return optionalAdapterId() == e.optionalAdapterId()
                && value == e.value
                && valueTotal == e.valueTotal
                && stockTotal == e.stockTotal
                && element_title.equals(e.element_title);
    }

至于效率如何,我认为任何迭代总是很昂贵的,这就是为什么每当我需要对大列表使用这个函数时,我都会在一个单独的线程上执行它的操作,并在一个线程上检索响应这需要它,即使知道在哪一点会很好,在不同的线程上执行它是否有益,需要这种线程的项目数量是多少,该信息将添加到文档中。< /p>

答案 17 :(得分:0)

这是一种比较两个考虑重复的集合的方法。因此,集合大小不一定相同。因此,它将在“实际”中查找“预期”:

    private static <T> boolean containsAllExpected(Collection<T> actual, Collection<T> expected) {
        if (actual == null && expected == null) {
            return true;
        }
        if (actual == null || expected == null) {
            return false;
        }
        Collection<T> a = new ArrayList<>(actual);
        Collection<T> e = new ArrayList<>(expected);

        Iterator<T> ei = e.iterator();
        while (ei.hasNext()) {
            T item = ei.next();
            if (a.contains(item)) {
                ei.remove();
                a.remove(item);
            } else {
                return false;
            }
        }

        return true;
    }

享受:)

答案 18 :(得分:-2)

这取决于您使用的具体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中所花费的时间。请记住,它会在每个元素进入时对其进行排序。

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