175

I have two ArrayLists of type Answer (self-made class).

I'd like to compare the two lists to see if they contain the same contents, but without order mattering.

Example:

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

List.equals states that two lists are equal if they contain the same size, contents, and order of elements. I want the same thing, but without order mattering.

Is there a simple way to do this? Or will I need to do a nested for loop, and manually check each index of both lists?

Note: I can't change them from ArrayList to another type of list, they need to remain that.

0xCursor
  • 2,242
  • 4
  • 15
  • 33
iaacp
  • 4,655
  • 12
  • 34
  • 39

20 Answers20

183

Probably the easiest way for any list would be:

listA.containsAll(listB) && listB.containsAll(listA)
  • 5
    Where is the fun in that. In all seriousness though this is probably the better solution. – Jacob Schoen Nov 21 '12 at 20:37
  • 79
    Depends on whether `[a, b, c]` and `[c, b, a, b]` are considered to have the same contents. This answer would say they do, but it could be that for the OP they don't (since one contains a duplicate and the other doesn't). To say nothing of the efficiency issues. – yshavit Nov 21 '12 at 20:48
  • 1
    Nice solution! sometimes like in my case I don't care if there is a duplicate or no... So thank you so much for your answer, and thank you for your comment @yshavit – Chris Sim Jun 17 '14 at 09:28
  • 4
    enhancing based on comments - System.out.println(((l1.size() == l2.size())&&l2.containsAll(l1)&&l1.containsAll(l2))); – Nrj Apr 07 '15 at 09:19
  • 9
    @Nrj , what about [1,2,2] and [2,1,1]? – ROMANIA_engineer Jul 24 '15 at 18:47
  • 5
    This approach has complexity of O(n^2). Consider two list which are in reverse order, for ex: [1,2,3] and [3,2,1]. For first element it will have to scan n elements, for second n-1 elements and so on. So complexity will be of order n^2. I think better way will be to sort and then use equals. It will have complexity of O(n * log(n)) – puneet Oct 15 '15 at 09:28
  • 1
    @Nrj if it's true that l1.size()==l2.size()) and l2.containsAll(l1) why even bother to check l1.containsAll(l2)? – simpleuser Jun 07 '17 at 16:16
  • @Nrj your solution fails for l1 = {a,a,b} and l2 = {a,b,b} – Danish Khan Jun 28 '17 at 20:42
  • @DanishKhan yes, duplicates are not handled. If this is a concern, probably use the approach mentioned in accepted answer and handle the same. – Nrj Jul 03 '17 at 17:27
  • Solution is clear, concise & works with lists of various types. But it doesn't handle listA, listB or both being null as Jacob Schoen's solution does. Using null checks from his solution followed by `containsAll` here works well when list can be null or are not sortable. – James Apr 12 '19 at 15:41
  • It is impressive how developers don't matter about performance. – IamDOM Jan 16 '20 at 07:55
  • This answer is similar to embedding a for loop in another for loop!. Even the correctness is not 100 percent as it can't correctly handle lists with duplicate elements. – Oluwasegun Wahaab May 25 '20 at 06:47
143

You could sort both lists using Collections.sort() and then use the equals method. A slighly better solution is to first check if they are the same length before ordering, if they are not, then they are not equal, then sort, then use equals. For example if you had two lists of Strings it would be something like:

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);
}
Jacob Schoen
  • 14,034
  • 15
  • 82
  • 102
  • 22
    Just remember not to destroy the order of the original list (as `Collections.sort` does) - i.e. pass a copy. – arshajii Nov 21 '12 at 20:10
  • @A.R.S. yes that is a definite side effect, but only if it matters in their particular case. – Jacob Schoen Nov 21 '12 at 20:11
  • 3
    You could just add `one = new ArrayList(one); two = new ArrayList(two);` to avoid ruining the arguments. – arshajii Nov 21 '12 at 20:14
  • @jschoen Trying to do Collections.sort() is giving me this error: Bound mismatch: The generic method sort(List) of type Collections is not applicable for the arguments (ArrayList). The inferred type Answer is not a valid substitute for the bounded parameter > – iaacp Nov 21 '12 at 20:21
  • `if (one == null && two == null){` is not necessary, because the constructors will throw NPE if they get `null` – jlordo Nov 21 '12 at 20:28
  • What Type do you have in the lists? You need to either make that class implement [`Comparable`](http://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html) or pass in a [`Comparator`](http://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html) to the sort method along with the list.See [How to use Comparator in Java to sort](http://stackoverflow.com/questions/2839137/how-to-use-comparator-in-java-to-sort) for more. – Jacob Schoen Nov 21 '12 at 20:29
  • @jlordo Fixed, well we do not want a NPE do we. – Jacob Schoen Nov 21 '12 at 20:31
  • @jlordo Maybe if I edit this enough I will finally get it right. – Jacob Schoen Nov 21 '12 at 20:33
  • @jschoen maybe you wish to change the signature to: `public static > boolean equalLists (List one, List two)` so it's generic – Marcus Junius Brutus Jan 17 '14 at 18:25
  • Will replacing `one.equals(two)` with `if(one.get(0) == two.get(0) && one.get(one.size()-1) == two.get(one.size()-1)) return true; else return false;` give more optimized code? – Shivang Doshi Sep 20 '15 at 09:48
  • @JacobSchoen I think you are missing some parens in your conditional. It should be: if((one == null && two != null) || (one != null && two == null) || one.size() != two.size()){ – Jeremy Goodell Nov 12 '15 at 23:45
  • Wouldnt it be better to check for the list size instead of if they are null, and then just set the params as non-null? – portfoliobuilder Sep 21 '16 at 19:47
  • 5
    Second "if" statement inside function can be simplified as `if(one == null || two == null || one.size() != two.size()){ return false; }` because you are already checking if both one and two are null – Hugo Dec 28 '16 at 10:13
  • how come this solution is accepted as an answer. user381105's solution is more efficient and clear. – saurabh gupta Apr 27 '20 at 11:09
110

Apache Commons Collections to the rescue once again:

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

Docs:

org.apache.commons.collections4.CollectionUtils

public static boolean isEqualCollection(java.util.Collection a,
                                        java.util.Collection b)
Returns `true` iff the given `Collection`s contain exactly the same elements with exactly the same cardinalities.

That is, iff the cardinality of e in a is equal to the cardinality of e in b, for each element e in a or b.

Parameters:

  • a - the first collection, must not be null
  • b - the second collection, must not be null

Returns: true iff the collections contain the same elements with the same cardinalities.

informatik01
  • 16,038
  • 10
  • 74
  • 104
acdcjunior
  • 132,397
  • 37
  • 331
  • 304
  • The implementation seems more or less similar with DiddiZ's answer. – user227353 Aug 27 '15 at 16:48
  • OK... but what about getting hold of the culprits (elements which are not common to the two lists) if the answer is `false`? See my answer. – mike rodent Dec 12 '16 at 21:16
  • `implementation 'org.apache.commons:commons-collections4:4.3'` has left me with an error `Caused by: com.android.builder.dexing.DexArchiveBuilderException: Failed to process` **path**. – Aliton Oliveira Aug 13 '19 at 01:41
17
// 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;
}
Community
  • 1
  • 1
cHao
  • 84,970
  • 20
  • 145
  • 172
  • Wow, was really surprised to find this performing faster than all other solutions. And it supports early-out. – DiddiZ Dec 01 '13 at 19:30
  • This could also short-circuit in the second loop, if `count` becomes negative, simplifying the loop body to `if(!counts.containsKey(item) || --counts.get(item).count < 0) return false;` Also, the 3rd loop could be simplified to `for(Count c: counts.values()) if(c.count != 0) return false;` – Holger Jun 12 '19 at 10:25
  • @Holger I'd considered something akin to the first (removing the count when it hits zero, which would have the same effect but also turn the checks at the end into "return whether `counts` is empty"), but didn't want to obscure the main point: that using a map basically turns this into an O(N+M) problem, and is the single biggest boost you're likely to get. – cHao Jun 12 '19 at 11:13
  • @cHao yes, you deserve the credits for pointing to a solution with a better time complexity than previous ones. I just happened to think about it, because there’s a recent similar question regarding iterables. Since we also now have Java 8, it was worth rethinking it. If you short-circuit in the second loop when the number becomes negative, the third loop becomes obsolete. Further, avoiding boxing might be a double-edged sword, with new `Map.merge`, using boxed integers might be simpler and more efficient for most use cases. See also [this answer](https://stackoverflow.com/a/56561351/2711488)… – Holger Jun 12 '19 at 11:46
  • Together with the short-circuiting from Holger (returning when a count gets negative) you can actually completely omit the third loop. If the collections have the same size and you subtracted "size times" 1 in the second loop without going negative or finding an element not in the first collection, all must be zero again. Of course that does not change the complexity, but it completely removed the need to create any kind of view of the map contents. – RcCookie May 31 '23 at 13:46
14

I'd say these answers miss a trick.

Bloch, in his essential, wonderful, concise Effective Java, says, in item 47, title "Know and use the libraries", "To summarize, don't reinvent the wheel". And he gives several very clear reasons why not.

There are a few answers here which suggest methods from CollectionUtils in the Apache Commons Collections library but none has spotted the most beautiful, elegant way of answering this question:

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

}

Culprits: i.e. the elements which are not common to both Lists. Determining which culprits belong to list1 and which to list2 is relatively straightforward using CollectionUtils.intersection( list1, culprits ) and CollectionUtils.intersection( list2, culprits ).
However it tends to fall apart in cases like { "a", "a", "b" } disjunction with { "a", "b", "b" } ... except this is not a failing of the software, but inherent to the nature of the subtleties/ambiguities of the desired task.

You can always examine the source code (l. 287) for a task like this, as produced by the Apache engineers. One benefit of using their code is that it will have been thoroughly tried and tested, with many edge cases and gotchas anticipated and dealt with. You can copy and tweak this code to your heart's content if need be.


NB I was at first disappointed that none of the CollectionUtils methods provides an overloaded version enabling you to impose your own Comparator (so you can redefine equals to suit your purposes).

But from collections4 4.0 there is a new class, Equator which "determines equality between objects of type T". On examination of the source code of collections4 CollectionUtils.java they seem to be using this with some methods, but as far as I can make out this is not applicable to the methods at the top of the file, using the CardinalityHelper class... which include disjunction and intersection.

I surmise that the Apache people haven't got around to this yet because it is non-trivial: you would have to create something like an "AbstractEquatingCollection" class, which instead of using its elements' inherent equals and hashCode methods would instead have to use those of Equator for all the basic methods, such as add, contains, etc. NB in fact when you look at the source code, AbstractCollection does not implement add, nor do its abstract subclasses such as AbstractSet... you have to wait till the concrete classes such as HashSet and ArrayList before add is implemented. Quite a headache.

In the mean time watch this space, I suppose. The obvious interim solution would be to wrap all your elements in a bespoke wrapper class which uses equals and hashCode to implement the kind of equality you want... then manipulate Collections of these wrapper objects.

mike rodent
  • 14,126
  • 11
  • 103
  • 157
  • Also, someone wise said "Know the cost of dependency" – Stanislaw Baranski Aug 06 '18 at 00:10
  • @StanislawBaranski That's an interesting comment. Is it a suggestion that one shouldn't be too dependent on such libraries? When you use an OS on a computer that's already a huge leap of faith isn't it? The reason I am happy to use the Apache libraries is because I take them to be very high quality indeed, and assume that their methods comply with their "contract" and have been thoroughly tested. How much time would you take developing your own code which you trusted more? Copying the code from the open-source Apache libraries and scrutinising it might be something to think about... – mike rodent Sep 22 '19 at 13:03
11

If the cardinality of items doesn't matter (meaning: repeated elements are considered as one), then there is a way to do this without having to sort:

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

This will create a Set out of each List, and then use HashSet's equals method which (of course) disregards ordering.

If cardinality matters, then you must confine yourself to facilities provided by List; @jschoen's answer would be more fitting in that case.

shmosel
  • 49,289
  • 6
  • 73
  • 138
Isaac
  • 16,458
  • 5
  • 57
  • 81
  • what if listA = [a, b, c, c], and listB = [ a, b, c]. result will be true, but lists are not equals. – Nikolas Nov 28 '19 at 13:24
8

Converting the lists to Guava's Multiset works very well. They are compared regardless of their order and duplicate elements are taken into account as well.

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));
Natix
  • 14,017
  • 7
  • 54
  • 69
7

This is based on @cHao solution. I included several fixes and performance improvements. This runs roughly twice as fast the equals-ordered-copy solution. Works for any collection type. Empty collections and null are regarded as equal. Use to your advantage ;)

/**
 * 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;
}
DiddiZ
  • 625
  • 8
  • 17
  • You can skip the final for-loop using a sum counter. The sum counter will count the total of counts at each stage. Increase the sum counter in the first for-loop, and decrease it in the second for-loop. If the sum counter is greater than 0, the lists don't match, otherwise they do. Currently, in the final for-loop you check if all counts are zero or in other words, if the sum of all counts is zero. Using the sum counter kind of reverses this check, returning true if the total of counts is zero, or false otherwise. – SatA Jun 01 '16 at 15:42
  • IMO, it is worth skipping that for-loop since when the lists do match (worst-case scenario) the for-loop adds another unnecessary O(n). – SatA Jun 01 '16 at 15:42
  • @SatA actually, you can remove the 3rd loop without any replacement. The second loop does already return `false` when a key does not exist or its count becomes negative. Since the total size of both lists match (this has been checked upfront), it is impossible to have non-zero values after the second loop, as there can’t be positive values for a key without negative values for another key. – Holger Jun 12 '19 at 10:36
  • @holger it seems you are absolutely correct. As far as I can tell, the 3rd loop isn't necessary at all. – SatA Jun 13 '19 at 11:25
  • @SatA …and with Java 8, this can be implemented concisely, like in [this answer](https://stackoverflow.com/a/56561351/2711488). – Holger Jun 13 '19 at 11:36
  • @Holger Nice catch. – DiddiZ Jun 13 '19 at 12:23
6

Think how you would do this yourself, absent a computer or programming language. I give you two lists of elements, and you have to tell me if they contain the same elements. How would you do it?

One approach, as mentioned above, is to sort the lists and then go element-by-element to see if they're equal (which is what List.equals does). This means either you're allowed to modify the lists or you're allowed to copy them -- and without knowing the assignment, I can't know if either/both of those are allowed.

Another approach would be to go through each list, counting how many times each element appears. If both lists have the same counts at the end, they have the same elements. The code for that would be to translate each list to a map of elem -> (# of times the elem appears in the list) and then call equals on the two maps. If the maps are HashMap, each of those translations is an O(N) operation, as is the comparison. That's going to give you a pretty efficient algorithm in terms of time, at the cost of some extra memory.

yshavit
  • 42,327
  • 7
  • 87
  • 124
5

I had this same problem and came up with a different solution. This one also works when duplicates are involved:

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

Advantages compared to some other solutions:

  • less than O(N²) complexity (although I have not tested it's real performance comparing to solutions in other answers here);
  • exits early;
  • checks for null;
  • works even when duplicates are involved: if you have an array [1,2,3,3] and another array [1,2,2,3] most solutions here tell you they are the same when not considering the order. This solution avoids this by removing equal elements from the temporary lists;
  • uses semantic equality (equals) and not reference equality (==);
  • does not sort itens, so they don't need to be sortable (by implement Comparable) for this solution to work.
Chalkos
  • 162
  • 2
  • 7
4

One-line method :)

  1. Collection's items NOT implement the interface Comparable<? 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. Collection's items implement the interface Comparable<? 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. support Android5 & Android6 via https://github.com/retrostreams/android-retrostreams

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

////Test case

    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
Yessy
  • 1,172
  • 1
  • 8
  • 13
3

If you don't hope to sort the collections and you need the result that ["A" "B" "C"] is not equals to ["B" "B" "A" "C"],

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

is not enough, you propably need to check the size too :

    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);
}
JaskeyLam
  • 15,405
  • 21
  • 114
  • 149
2

Solution which leverages CollectionUtils subtract method:

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

If you care about order, then just use the equals method:

list1.equals(list2)

If you don't care order then use this

Collections.sort(list1);
Collections.sort(list2);      
list1.equals(list2)
Ramesh Bhati
  • 1,239
  • 16
  • 25
1

I think this is a simple solution:

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));
}
Tomislav Brabec
  • 529
  • 2
  • 14
  • 20
0

Best of both worlds [@DiddiZ, @Chalkos]: this one mainly builds upon @Chalkos method, but fixes a bug (ifst.next()), and improves initial checks (taken from @DiddiZ) as well as removes the need to copy the first collection (just removes items from a copy of the second collection).

Not requiring a hashing function or sorting, and enabling an early exist on un-equality, this is the most efficient implementation yet. That is unless you have a collection length in the thousands or more, and a very simple hashing function.

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;
}
jazzgil
  • 2,250
  • 2
  • 19
  • 20
  • 1
    If I am not mistaken, the complexity of this algorithm in a worth case scenario (where the lists are equal but sorted in an opposite manner) would be O(N*N!). – SatA Jun 05 '16 at 11:39
  • Actually, it would be O(N*(N/2)), as with each iteration, the array size decreases. – jazzgil Jun 06 '16 at 08:47
  • @jazzgil That's the same as O(n^2), since constant factors like dividing by 2 don't count. – David Conrad Jun 10 '22 at 16:06
0

It is an alternative way to check equality of array lists which can contain null values:

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);
}
Ayaz Alifov
  • 8,334
  • 4
  • 61
  • 56
0

My solution for this. It is not so cool, but works well.

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();
}
Cícero Moura
  • 2,027
  • 1
  • 24
  • 36
0

Assuming if we can modify one list, with the support of java 8 removeIf

  assertTrue(listA.size(), listB.size());
  listB.removeIf(listA::contains);
  assertTrue(listB.isEmpty());
Asanka Siriwardena
  • 871
  • 13
  • 18
-1

In that case lists {"a", "b"} and {"b","a"} are equal. And {"a", "b"} and {"b","a","c"} are not equal. If you use list of complex objects, remember to override equals method, as containsAll uses it inside.

if (oneList.size() == secondList.size() && oneList.containsAll(secondList)){
        areEqual = true;
}
Andrew
  • 2,438
  • 1
  • 22
  • 35
  • -1: gives the wrong answer with {"a", "a", "b"} and {"a", "b", "b"} : check out source code for `AbstractCollection.containsAll()`. You have to allow for having duplicate elements as we are talking about `Lists`, not about `Sets`. Please see my answer. – mike rodent Dec 12 '16 at 21:07