27

I have two String arrays, let's say:

String[] s1 = {"a","b","c"}
String[] s2 = {"c","a","b"} 

//these arrays should be equal

I wanted to check their equality in the "cleanest" way.

I tried using Arrays.equals(s1,s2) but I'm getting a false answer. I guess that this method cares about the elements' order and I don't want that to matter.

Can you please tell me how can I do that in a nice way?

Null
  • 1,950
  • 9
  • 30
  • 33
Popokoko
  • 6,413
  • 16
  • 47
  • 58

10 Answers10

31
  • Arrays.sort(s1);
  • Arrays.sort(s2);
  • Arrays.equals(s1,s2);

In case you do not want to modify the original arrays

 Arrays.equals( Arrays.sort( Arrays.copyof(s1,s1.length)),
                Arrays.sort( Arrays.copyof(s2,s2.length)) );

Arrays.sort() uses an optimized quick sort which is nlog(n) for average but O(n2) in worst case. From the java docs. So the worst case it will O(n2) but practically it will be O(nlogn) for most of the cases.

The sorting algorithm is a tuned quicksort, adapted from Jon L. Bentley and M. Douglas McIlroy's "Engineering a Sort Function", Software-Practice and Experience, Vol. 23(11) P. 1249-1265 (November 1993). This algorithm offers n*log(n) performance on many data sets that cause other quicksorts to degrade to quadratic performance.

MoveFast
  • 3,011
  • 2
  • 27
  • 53
  • 1
    @ManojGumber I too think its a great solution. – KK. Apr 14 '12 at 14:28
  • I agree with that, but i guess the complexity would be 2N + N^2 which is equivalent to N^2 that i cant avoid from, right? – Popokoko Apr 14 '12 at 14:33
  • 2
    `Arrays.sort(int[])` performs quicksorts. `Arrays.sort(Object[])`, however, performs a merge sort... – Lukas Eder Apr 14 '12 at 15:07
  • for completeness, if you do not want to modify original arrays the following code compiles (in opposition to the one given above) : `boolean unorderedArrayEqual(Object[] s1, Object[] s2) { Object[] c1 = Arrays.copyOf(s1,s1.length); Object[] c2 = Arrays.copyOf(s2,s2.length); Arrays.sort(c1); Arrays.sort(c2); return Arrays.equals(c1, c2); }` – jolivier May 31 '12 at 10:06
  • This solution assumes that the array type implements `Comparable` – Bohemian Feb 15 '14 at 23:53
  • 13
    I can't believe nobody has pointed out that `Arrays.sort` *does* modify the list, and it returns `void` so it can't even be used as shown in this answer. – aioobe Jun 13 '15 at 21:27
  • Better than `Arrays.copyOf()`, simply use `s1.clone()`! – Nayuki Aug 22 '16 at 02:58
12

Others have suggested sorting the arrays. But since you're looking for the "cleanest" solution, I think the original arrays shouldn't be touched. Hence:

List<String> l1 = new ArrayList<String>(Arrays.asList(s1));
List<String> l2 = new ArrayList<String>(Arrays.asList(s2));

Collections.sort(l1);
Collections.sort(l2);

boolean outcome = l1.equals(l2);
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • Thank you but this is more or less equivalent to Samir Mangroliya and ManojGumber solution – Popokoko Apr 14 '12 at 14:32
  • 3
    @Popokoko: I think it is worth mentioning that a mere "equality" check probably shouldn't modify the original data. This code makes copies of the arrays and compares sorts only the copies – Lukas Eder Apr 14 '12 at 14:33
  • Oh i see and now i understand.. Hmm, in my case i dont mind if the array would be changed since it is a Junit test which repeats it's logic by initializing the arrays each time. – Popokoko Apr 14 '12 at 14:38
4

If you are using Eclipse Collections, you can use a Bag to figure out if the two arrays are equal.

String[] s1 = {"a", "b", "c", "c"};
String[] s2 = {"c", "a", "b", "c"};

Bag<String> h1 = Bags.mutable.with(s1);
Bag<String> h2 = Bags.mutable.with(s2);
Assert.assertEquals(h1, h2);

Bags (also known as multisets) are considered equal if they have the same number of occurrences of each element. Order doesn't matter, and it properly handles duplicate elements. The advantage of using a bag backed by a hashtable is that creating one takes linear time. Sorting both takes O(n log n).

Note: I am a committer for Eclipse Collections

Donald Raab
  • 6,458
  • 2
  • 36
  • 44
  • Thanks for pointing out the usage of Eclipse Collections. Using Apache Commons Collections should also apply to the question: [`CollectionsUtils.isEqualCollection(c1, c2)`](https://commons.apache.org/proper/commons-collections/javadocs/api-3.2.2/org/apache/commons/collections/CollectionUtils.html#isEqualCollection(java.util.Collection,%20java.util.Collection)) It checks the occurrence/cardinality of an element in c1 and c2, ignoring element order – Jens Kreidler Jan 22 '21 at 18:09
3
String[] s1 = {"a","b","c"};
String[] s2 = {"b","c","a"} ;

Arrays.sort(s1);
Arrays.sort(s2);

    if(Arrays.equals(s1, s2)){
        System.out.println("ok");
}
Samir Mangroliya
  • 39,918
  • 16
  • 117
  • 134
3

The human way:

Iterate over the first array, checking for the existence of each element in the second array, and then doing the same for the second array on the first array. Time: n^2. Note this method assumes that no element is repeated. If it was, you would have to, for each element you're checking, go back to the beginning and count how many instances of that element there are, (say X), and only count a success as finding the Xth element in the second array. Doing this would eliminate the need for the second check, and left as an exercise to the reader (if you're so inclined, that is.)

boolean equal(String[] arr1, String[] arr2) {
    if(arr1.length != arr2.length) return false; // obviously
    main_loop:
    for(int i = 0; i < arr1.length; i++) {
        for(int j = 0; j < arr2.length; j++) {
            if(arr1[i].equals(arr2[j]))
                break main_loop;
        }
        return false;
    }
    main_loop:
    for(int i = 0; i < arr2.length; i++) {
        for(int j = 0; j < arr1.length; j++) {
            if(arr2[i].equals(arr1[j]))
                break main_loop;
        }
        return false;
    }
    // having got through both loops, we can now return true
}

A more advanced way: sort both arrays and walk over both of them. Time: n lg n

boolean equals(String[] arr1, String[] arr2) {
    if(arr1.length != arr2.length) return false;
    String[] copy1 = Arrays.copyOf(arr1,arr1.length); // java.util.Arrays
    String[] copy2 = Arrays.copyOf(arr2,arr2.length); // java.util.Arrays
    Arrays.sort(copy1);
    Arrays.sort(copy2);
    for(int i = 0; i < copy1.length; i++) {
        if(!copy1[i].equals(copy2[i])
            return false;
    }
    return true;
}

An even more advanced way: use a hashmap, adding for the counts of the first string array, removing for the counts of the second string array. When you're odne all counts should be zero.

boolean equal(String[] arr1, String[] arr2) {
    if(arr1.length != arr2.length) return false;
    Map<String, Integer> map1 = new HashMap<String,Integer>();
    for(String str : arr1) {
        if(!map.containsKey(str)) {
            map.put(str, 1);
        } else {
            map.put(str, map.get(str) + 1); // add to count inthe map
        }
    }
    for(String str : arr1) {
        if(!map.containsKey(str)) {
            return false; // we have an element in arr2 not in arr1 - leave now
        } else {
            map.put(str, map.get(str) - 1); // remove to count inthe map
        }
    }
    for(Integer count : map.values()) {
        if(count.intValue() != 0) return false;
    }
    return true;
}
corsiKa
  • 81,495
  • 25
  • 153
  • 204
3

Set::equals

NB: This is a simple non-intrusive solution, but it only works if you are sure there are no duplicate entries in either of the input arrays/lists (or you want to ignore duplicates).

You don't need any external libraries for this one. Set<> already has an equals method that does order-independent comparison.

public static <T> boolean areArraysEquivalent(T[] ary1, T[] ary2) {
    if (ary1 == null) {
        return ary2 == null;
    }

    if (ary2 == null) {
        return false;
    }

    List<T> list1 = Arrays.asList(ary1);
    List<T> list2 = Arrays.asList(ary2);
    return areListsEquivalent(list1, list2);
}


public static <T> boolean areListsEquivalent(List<T> list1, List<T> list2) {
    if (list1 == null) {
        return list2 == null;
    }

    if (list2 == null) {
        return false;
    }

    Set<T> set1 = new HashSet<>(list1);
    Set<T> set2 = new HashSet<>(list2);
    return set1.equals(set2);
}
vonWippersnap
  • 483
  • 3
  • 9
  • Your solution would output true for `["a", "a"]` is equivalent to `["a"]` – Czar Aug 07 '18 at 15:48
  • Edited to state assumptions. We can't fix this with an input length check either, because `["a", "a", "b"]` would still be equivalent to `["a", "b", "b"]`. But the approach is still useful for a limited use-case. – vonWippersnap Aug 10 '18 at 22:29
1

I suppose this is for school.

Possible strategies :

  • use Arrays.sort to sort both arrays and then use a loop to compare s1[i] to s2[i]
  • use a loop and for each item of s1 look at the items of s2 to find if it contains the same
  • put items of s1 into a hashset and then use a loop on s2 and look if your items are in s1
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
1

I'd sort the 2 arrays first, then compare line-by-line...

public boolean areArraysEqual (String[] array1,String[] array2){    
    if (s1.length != s2.length){
        return false;
        }

    java.util.Arrays.sort(s1);
    java.util.Arrays.sort(s2);

    for (int i=0;i<s1.length;i++){
        if (! s1[i].equals(s2[i])){
            return false;
        }
    }

return true;
}
wattostudios
  • 8,666
  • 13
  • 43
  • 57
0

If one will frequently be wanting to compare arrays against each other without modifying their contents, it may be helpful to define a type which encapsulates an immutable array, a sorted version thereof, a long sequence count which is guaranteed unique and at least mostly correlates with object age, and an initially-null reference to another older object which is known to match. It may also be helpful to cache a hash value which combines the hash values of all array elements.

Using such an approach, sorting would be required the first time an objects is compared to something (anything) else, but not after that. Further, if objects X and Y are both found equal to Z, then comparison between X and Y could report them as equal without having to actually examine the array contents (if Z is older than X and Y, then both will report themselves as equal to the same older object; if X is the youngest and Y the oldest, X will know it's equal to Z and Z will know it's equal to Y. When X is next compared to something, it will find out that the oldest thing it's known to be equal to is Y, so it of course would be equal to Y.

Such an approach would yield equality-comparison performance benefits similar to interning, but without the need for an interning dictionary.

supercat
  • 77,689
  • 9
  • 166
  • 211
0

For small arrays, I would use Arrays.sort and Arrays.equals as others have suggested. For larger arrays you can use the following solution which has better time complexity - O(n) rather than O(n log n).

public static boolean haveSameElements(Object[] arr1, Object[] arr2) {
    return arr1.length == arr2.length && counts(arr1).equals(counts(arr2));
}

// Map.merge and method references require Java 8
private static <T> Map<T, Integer> counts(T[] arr) {
    Map<T, Integer> map = new HashMap<>();
    for (T t : arr)
        map.merge(t, 1, Integer::sum);
    return map;
}
Paul Boddington
  • 37,127
  • 10
  • 65
  • 116