Reputation: 6543
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
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
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
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
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
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
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
Reputation: 3015
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
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
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
Reputation: 382092
I suppose this is for school.
Possible strategies :
Upvotes: 1