JavaArrayList-我怎样才能知道两个列表是否相等,顺序是否无关紧要?

我有两个 ArrayList类型的 Answer(自制类)。

我想比较一下这两个列表,看看它们是否包含相同的内容,但是顺序不重要。

例如:

//These should be equal.
ArrayList<String> listA = {"a", "b", "c"}
ArrayList<String> listB = {"b", "c", "a"}

List.equals指出,如果两个列表包含相同的元素大小、内容和顺序,则它们是相等的。我想要同样的东西,但没有命令的重要性。

有什么简单的方法吗?或者我需要做一个嵌套的 for 循环,并手动检查两个列表的每个索引?

注意: 我不能将它们从 ArrayList更改为另一种类型的列表,它们需要保持这种状态。

276436 次浏览

您可以使用 Collections.sort()对两个列表进行排序,然后使用 equals 方法。一个更好的解决方案是在排序之前首先检查它们的长度是否相同,如果不相同,则它们不相等,然后排序,然后使用等于。例如,如果您有两个 String 列表,那么类似于:

public  boolean equalLists(List<String> one, List<String> two){
if (one == null && two == null){
return true;
}


if((one == null && two != null)
|| one != null && two == null
|| one.size() != two.size()){
return false;
}


//to avoid messing the order of the lists we will use a copy
//as noted in comments by A. R. S.
one = new ArrayList<String>(one);
two = new ArrayList<String>(two);


Collections.sort(one);
Collections.sort(two);
return one.equals(two);
}

想想如果没有计算机或编程语言,你自己会怎么做。我给你两个元素列表,你必须告诉我它们是否包含相同的元素。你会怎么做?

如上所述,一种方法是对列表进行排序,然后逐个元素查看它们是否相等(List.equals就是这样做的)。这意味着要么允许修改列表,要么允许复制它们——在不知道作业的情况下,我无法知道这两者是否都允许。

另一种方法是遍历每个列表,计算每个元素出现的次数。如果两个列表末尾的计数相同,则它们具有相同的元素。这样做的代码是将每个列表转换为 elem -> (# of times the elem appears in the list)的映射,然后在两个映射上调用 equals。如果映射是 HashMap,则每个转换都是 O (N)操作,比较也是如此。这将给你一个非常有效的算法在时间方面,代价是一些额外的内存。

如果项目的基数不重要(意思是: 重复的元素被认为是一个元素) ,那么有一种方法可以做到这一点,而不必排序:

boolean result = new HashSet<>(listA).equals(new HashSet<>(listB));

这将从每个 List中创建一个 Set,然后使用 HashSetequals方法(当然)忽略排序。

如果基数很重要,那么您必须将自己局限于 List提供的设施; 在这种情况下,@jSchoen 的答案更合适。

对于 任何列表来说,最简单的方法可能是:

listA.containsAll(listB) && listB.containsAll(listA)
// helper class, so we don't have to do a whole lot of autoboxing
private static class Count {
public int count = 0;
}


public boolean haveSameElements(final List<String> list1, final List<String> list2) {
// (list1, list1) is always true
if (list1 == list2) return true;


// If either list is null, or the lengths are not equal, they can't possibly match
if (list1 == null || list2 == null || list1.size() != list2.size())
return false;


// (switch the two checks above if (null, null) should return false)


Map<String, Count> counts = new HashMap<>();


// Count the items in list1
for (String item : list1) {
if (!counts.containsKey(item)) counts.put(item, new Count());
counts.get(item).count += 1;
}


// Subtract the count of items in list2
for (String item : list2) {
// If the map doesn't contain the item here, then this item wasn't in list1
if (!counts.containsKey(item)) return false;
counts.get(item).count -= 1;
}


// If any count is nonzero at this point, then the two lists don't match
for (Map.Entry<String, Count> entry : counts.entrySet()) {
if (entry.getValue().count != 0) return false;
}


return true;
}

这是基于@cHao 解决方案的。我包括了一些修复和性能改进。这种方法的运行速度大约是等顺序复制解决方案的两倍。适用于任何集合类型。空集合和 null 被认为是相等的。利用你的优势;)

/**
* Returns if both {@link Collection Collections} contains the same elements, in the same quantities, regardless of order and collection type.
* <p>
* Empty collections and {@code null} are regarded as equal.
*/
public static <T> boolean haveSameElements(Collection<T> col1, Collection<T> col2) {
if (col1 == col2)
return true;


// If either list is null, return whether the other is empty
if (col1 == null)
return col2.isEmpty();
if (col2 == null)
return col1.isEmpty();


// If lengths are not equal, they can't possibly match
if (col1.size() != col2.size())
return false;


// Helper class, so we don't have to do a whole lot of autoboxing
class Count
{
// Initialize as 1, as we would increment it anyway
public int count = 1;
}


final Map<T, Count> counts = new HashMap<>();


// Count the items in col1
for (final T item : col1) {
final Count count = counts.get(item);
if (count != null)
count.count++;
else
// If the map doesn't contain the item, put a new count
counts.put(item, new Count());
}


// Subtract the count of items in col2
for (final T item : col2) {
final Count count = counts.get(item);
// If the map doesn't contain the item, or the count is already reduced to 0, the lists are unequal
if (count == null || count.count == 0)
return false;
count.count--;
}


// At this point, both collections are equal.
// Both have the same length, and for any counter to be unequal to zero, there would have to be an element in col2 which is not in col1, but this is checked in the second loop, as @holger pointed out.
return true;
}

利用 CollectionUtils 减法的解决方案:

import static org.apache.commons.collections15.CollectionUtils.subtract;


public class CollectionUtils {
static public <T> boolean equals(Collection<? extends T> a, Collection<? extends T> b) {
if (a == null && b == null)
return true;
if (a == null || b == null || a.size() != b.size())
return false;
return subtract(a, b).size() == 0 && subtract(a, b).size() == 0;
}
}

Apache 下议院收藏再次拯救了我们:

List<String> listA = Arrays.asList("a", "b", "b", "c");
List<String> listB = Arrays.asList("b", "c", "a", "b");
System.out.println(CollectionUtils.isEqualCollection(listA, listB)); // true


List<String> listC = Arrays.asList("a", "b", "c");
List<String> listD = Arrays.asList("a", "b", "c", "c");
System.out.println(CollectionUtils.isEqualCollection(listC, listD)); // false

文件:

org.apache.commons.collections4.CollectionUtils

public static boolean isEqualCollection(java.util.Collection a,
java.util.Collection b)
如果给定的“集合”包含完全相同的内容,返回“ true” 具有完全相同基数的元素。

也就是说,如果 中 <你好m>你好的基数等于 B中 <你好m>你好的基数 元素 B中的 <你好m>你好

参数:

  • 第一个集合,不能是 null
  • 第二个 收集,必须不是 null

返回: < strong > true,如果集合包含 具有相同基数的相同元素

我遇到了同样的问题,于是想出了一个不同的解决方案:

public static boolean equalsWithoutOrder(List<?> fst, List<?> snd){
if(fst != null && snd != null){
if(fst.size() == snd.size()){
// create copied lists so the original list is not modified
List<?> cfst = new ArrayList<Object>(fst);
List<?> csnd = new ArrayList<Object>(snd);


Iterator<?> ifst = cfst.iterator();
boolean foundEqualObject;
while( ifst.hasNext() ){
Iterator<?> isnd = csnd.iterator();
foundEqualObject = false;
while( isnd.hasNext() ){
if( ifst.next().equals(isnd.next()) ){
ifst.remove();
isnd.remove();
foundEqualObject = true;
break;
}
}


if( !foundEqualObject ){
// fail early
break;
}
}
if(cfst.isEmpty()){ //both temporary lists have the same size
return true;
}
}
}else if( fst == null && snd == null ){
return true;
}
return false;
}

与其他解决方案相比的优势:

  • 低于 O (N2)复杂度(尽管我还没有测试它与其他答案相比的真实性能) ;
  • 提早退出;
  • 检查是否为空;
  • 即使有重复的数组也可以工作: 如果你有一个数组 [1,2,3,3]和另一个数组 [1,2,2,3],大多数解决方案在不考虑顺序时会告诉你它们是一样的。此解决方案通过从临时列表中删除相等的元素来避免这种情况;
  • 使用语义相等(equals)而不是引用相等(==) ;
  • 不对 itens 进行排序,因此它们不需要是可排序的(通过 implement Comparable) ,这个解决方案就可以工作。

两全其美[@DiddiZ,@Chalkos ] : 这个主要建立在@Chalkos 方法的基础上,但是修复了一个 bug (ifst.next ()) ,改进了初始检查(取自@DiddiZ) ,并且消除了复制第一个集合的需要(只是从第二个集合的副本中删除项目)。

不需要散列函数或排序,并且支持早期存在的不等式,这是迄今为止效率最高的实现。除非您有一个数千或更长的集合长度,以及一个非常简单的散列函数。

public static <T> boolean isCollectionMatch(Collection<T> one, Collection<T> two) {
if (one == two)
return true;


// If either list is null, return whether the other is empty
if (one == null)
return two.isEmpty();
if (two == null)
return one.isEmpty();


// If lengths are not equal, they can't possibly match
if (one.size() != two.size())
return false;


// copy the second list, so it can be modified
final List<T> ctwo = new ArrayList<>(two);


for (T itm : one) {
Iterator<T> it = ctwo.iterator();
boolean gotEq = false;
while (it.hasNext()) {
if (itm.equals(it.next())) {
it.remove();
gotEq = true;
break;
}
}
if (!gotEq) return false;
}
// All elements in one were found in two, and they're the same size.
return true;
}

将列表转换为番石榴的 多集非常有效。不管它们的顺序如何,它们都要进行比较,重复的元素也要考虑在内。

static <T> boolean equalsIgnoreOrder(List<T> a, List<T> b) {
return ImmutableMultiset.copyOf(a).equals(ImmutableMultiset.copyOf(b));
}


assert equalsIgnoreOrder(ImmutableList.of(3, 1, 2), ImmutableList.of(2, 1, 3));
assert !equalsIgnoreOrder(ImmutableList.of(1), ImmutableList.of(1, 1));

这是检查可以包含空值的数组列表是否相等的另一种方法:

List listA = Arrays.asList(null, "b", "c");
List listB = Arrays.asList("b", "c", null);


System.out.println(checkEquality(listA, listB)); // will return TRUE




private List<String> getSortedArrayList(List<String> arrayList)
{
String[] array = arrayList.toArray(new String[arrayList.size()]);


Arrays.sort(array, new Comparator<String>()
{
@Override
public int compare(String o1, String o2)
{
if (o1 == null && o2 == null)
{
return 0;
}
if (o1 == null)
{
return 1;
}
if (o2 == null)
{
return -1;
}
return o1.compareTo(o2);
}
});


return new ArrayList(Arrays.asList(array));
}


private Boolean checkEquality(List<String> listA, List<String> listB)
{
listA = getSortedArrayList(listA);
listB = getSortedArrayList(listB);


String[] arrayA = listA.toArray(new String[listA.size()]);
String[] arrayB = listB.toArray(new String[listB.size()]);


return Arrays.deepEquals(arrayA, arrayB);
}

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

list1.equals(list2)

如果你不在乎秩序,那就用这个

Collections.sort(list1);
Collections.sort(list2);
list1.equals(list2)

我得说这些答案漏掉了一个窍门。

布洛赫在他的基本的、精彩的、简洁的 有效的爪哇中说,在第47项中,题目是“了解和使用库”,“总结起来,不要重新发明轮子”。他给出了几个非常明确的理由。

这里有一些答案,它们建议使用 Apache Commons Collection 库中的 CollectionUtils方法,但没有一个方法发现了 回答这个问题的最美丽,最优雅的方式:

Collection<Object> culprits = CollectionUtils.disjunction( list1, list2 );
if( ! culprits.isEmpty() ){
// ... do something with the culprits, i.e. elements which are not common


}

罪魁祸首 : 即不同于 Lists的元素。使用 CollectionUtils.intersection( list1, culprits )CollectionUtils.intersection( list2, culprits )判断哪些罪犯属于 list1,哪些属于 list2是相对简单的。
然而,在像{“ a”,“ a”,“ b”} disjunction和{“ a”,“ b”,“ b”}这样的情况下,它往往会分崩离析... ... 除非这不是软件的失败,而是所期望任务的微妙性/模糊性的本质所固有的。

对于这样的任务,您总是可以检查 源代码(l.287) ,正如 Apache 工程师所做的那样。使用他们的代码的一个好处是,它将被彻底地尝试和测试,许多边缘案例和陷阱预期和处理。如果需要的话,你可以复制和调整这些代码,使其符合你的心意。


起初我感到失望的是,没有一个 CollectionUtils方法提供一个重载版本,使您能够强制使用自己的 Comparator(因此您可以重新定义 equals以满足您的目的)。

但是在 Collections44.0中有一个新的类 Equator,它“确定 T 类型对象之间的相等性”。在对 Collections4 CollectionUtils.java 的源代码进行检查时,他们似乎使用了一些方法,但据我所知,这并不适用于文件顶部的方法,使用的是包括 disjunctionintersection在内的 CardinalityHelper类。

我推测 Apache 人员还没有考虑到这一点,因为它是非常重要的: 您必须创建一个类似于“ AbstractEquatingCollection”的类,它不使用其元素固有的 equalshashCode方法,而是必须对所有基本方法使用 Equator方法,如 addcontains等。注意,实际上当你查看源代码时,AbstractCollection并没有实现 add,也没有实现它的抽象子类,比如 AbstractSet... ... 你必须等到具体的类,比如 HashSetArrayList才能实现 add。真头疼。

与此同时,看看这个空间,我想。显而易见的临时解决方案是将所有元素封装在一个定制的包装器类中,该类使用 equalshashCode来实现您想要的那种相等性... 然后操作这些包装器对象的 Collections

在这种情况下,列表{“ a”,“ b”}和{“ b”,“ a”}是相等的。而{“ a”,“ b”}和{“ b”,“ a”,“ c”}是不相等的。如果使用复杂对象列表,请记住重写 等于方法,因为 包含所有在内部使用它。

if (oneList.size() == secondList.size() && oneList.containsAll(secondList)){
areEqual = true;
}

如果您不希望对集合进行排序,并且需要结果[“ A”“ B”“ C”]不等于[“ B”“ B”“ A”“ C”] ,

l1.containsAll(l2)&&l2.containsAll(l1)

还不够,你可能还需要检查尺寸:

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


System.out.println(l1.containsAll(l2)&&l2.containsAll(l1));//cautions, this will be true
System.out.println(isListEqualsWithoutOrder(l1,l2));//false as expected


System.out.println(l3.containsAll(l2)&&l2.containsAll(l3));//true as expected
System.out.println(isListEqualsWithoutOrder(l2,l3));//true as expected




public static boolean isListEqualsWithoutOrder(List<String> l1, List<String> l2) {
return l1.size()==l2.size() && l1.containsAll(l2)&&l2.containsAll(l1);
}

我的解决方案。它不是那么酷,但工作得很好。

public static boolean isEqualCollection(List<?> a, List<?> b) {


if (a == null || b == null) {
throw new NullPointerException("The list a and b must be not null.");
}


if (a.size() != b.size()) {
return false;
}


List<?> bCopy = new ArrayList<Object>(b);


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


for (int j = 0; j < bCopy.size(); j++) {
if (a.get(i).equals(bCopy.get(j))) {
bCopy.remove(j);
break;
}
}
}


return bCopy.isEmpty();
}

单行方法:)

  1. 集合的条目没有实现接口 Compaable < ? super T >

     static boolean isEqualCollection(Collection<?> a, Collection<?> b) {
    return a == b || (a != null && b != null && a.size() == b.size()
    && a.stream().collect(Collectors.toMap(Function.identity(), s -> 1L, Long::sum)).equals(b.stream().collect(Collectors.toMap(Function.identity(), s -> 1L, Long::sum))));
    }
    
  2. 集合的项实现了接口 Compable < ? super T >

     static <T extends Comparable<? super T>> boolean  isEqualCollection2(Collection<T> a, Collection<T> b) {
    return a == b || (a != null && b != null && a.size() == b.size() && a.stream().sorted().collect(Collectors.toList()).equals(b.stream().sorted().collect(Collectors.toList())));
    }
    
  3. 通过 https://github.com/retrostreams/android-retrostreams 支持 android 5和 android 6

    static boolean isEqualCollection(Collection<?> a, Collection<?> b) {
    return a == b || (a != null && b != null && a.size() == b.size()
    && StreamSupport.stream(a).collect(Collectors.toMap(Function.identity(), s->1L, Longs::sum)).equals(StreamSupport.stream(b).collect(Collectors.toMap(Function.identity(), s->1L, Longs::sum))));
    }
    

////测试用例

    boolean isEquals1 = isEqualCollection(null, null); //true
boolean isEquals2 = isEqualCollection(null, Arrays.asList("1", "2")); //false
boolean isEquals3 = isEqualCollection(Arrays.asList("1", "2"), null); //false
boolean isEquals4 = isEqualCollection(Arrays.asList("1", "2", "2"), Arrays.asList("1", "1", "2")); //false
boolean isEquals5 = isEqualCollection(Arrays.asList("1", "2"), Arrays.asList("2", "1")); //true
boolean isEquals6 = isEqualCollection(Arrays.asList("1", 2.0), Arrays.asList(2.0, "1")); //true
boolean isEquals7 = isEqualCollection(Arrays.asList("1", 2.0, 100L), Arrays.asList(2.0, 100L, "1")); //true
boolean isEquals8 = isEqualCollection(Arrays.asList("1", null, 2.0, 100L), Arrays.asList(2.0, null, 100L, "1")); //true

我认为这是一个简单的解决方案:

public boolean equalLists(List<String> one, List<String> two) {
if (one == null && two == null) {
return true;
}
    

if (one == null || two == null) {
return false;
}
    

return new HashSet<>(one).equals(new HashSet<>(two));
}