Popokoko
Popokoko

Reputation: 6543

Java: Checking equality of arrays (order doesn't matter)

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?

Upvotes: 29

Views: 19818

Answers (10)

Donald Raab
Donald Raab

Reputation: 6686

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

Upvotes: 4

vonWippersnap
vonWippersnap

Reputation: 523

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

Upvotes: 3

Paul Boddington
Paul Boddington

Reputation: 37645

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

Upvotes: 0

supercat
supercat

Reputation: 81115

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.

Upvotes: 0

corsiKa
corsiKa

Reputation: 82559

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

Upvotes: 3

wattostudios
wattostudios

Reputation: 8764

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

Upvotes: 1

MoveFast
MoveFast

Reputation: 3015

  • 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.

Upvotes: 30

Lukas Eder
Lukas Eder

Reputation: 220762

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

Upvotes: 13

Samir Mangroliya
Samir Mangroliya

Reputation: 40406

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

Upvotes: 3

Denys S&#233;guret
Denys S&#233;guret

Reputation: 382092

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

Upvotes: 1

Related Questions