I have two Collections in a Java class.The first collection contains previous data, the second contains updated data from the previous collection.
I would like to compare the two collections but I'm not sure of the best way to implement this efficiently.Both collections will contain the same amount of items.
Based then on the carType being the same in each collection I want to execute the carType method.
Any help is appreciated
Difficult to help, because you didn't tell us how you like to compare the (equal-size) collections. Some ideas, hoping one will fit:
Compare both collections if they contain the same objects in the same order
Iterator targetIt = target.iterator();
for (Object obj:source)
if (!obj.equals(targetIt.next()))
// compare result -> false
Compare both collections if they contain the same objects in the any order
for (Object obj:source)
if (target.contains(obj))
// compare result -> false
Find elements in other collection that has changed
Iterator targetIt = target.iterator();
for (Object obj:source)
if (!obj.equals(targetIt.next())
// Element has changed
Based on your comment, this algorithm would do it. It collects all Cars that have been updated. If the method result is an empty list, both collections contain equal entries in the same order. The algorithm relies on a correct implementation of equals() on the Car type!
public List<Car> findUpdatedCars(Collection<Car> oldCars, Collection<Car> newCars)
List<Car> updatedCars = new ArrayList<Car>();
Iterator oldIt = oldCars.iterator();
for (Car newCar:newCars) {
if (!newCar.equals(oldIt.next()) {
updatedCars.add(newCar);
}
}
return updatedCars;
}
From the set arithmetics, the sets A and B are equal iff A subsetequal B and B subsetequal A. So, in Java, given two collections A and B you can check their equality without respect to the order of the elements with
boolean collectionsAreEqual = A.containsAll(B) && B.containsAll(A);
Iterate over the first collection and add it into a Map<Entity, Integer> whereby Entity is the class being stored in your collection and the Integer represents the number of times it occurs.
Iterate over the second collection and, for each element attempt to look it up in the Map - If it exists then decrement the Integer value by one and perform any action necessary when a match is found. If the Integer value has reached zero then remove the (Entity, Integer) entry from the map.
This algorithm will run in linear time assuming you've implemented an efficient hashCode() method.
Slightly updated one considering null values:
static <T> boolean equals(Collection<T> lhs, Collection<T> rhs) {
boolean equals = false;
if(lhs!=null && rhs!=null) {
equals = lhs.size( ) == rhs.size( ) && lhs.containsAll(rhs) && rhs.containsAll(lhs);
} else if (lhs==null && rhs==null) {
equals = true;
}
return equals;
}
If not worried about cases like (2,2,3), (2,3,3):
static <T> boolean equals(Collection<T> lhs, Collection<T> rhs) {
return lhs.size( ) == rhs.size( ) && lhs.containsAll(rhs) && rhs.containsAll(lhs);
}
public static boolean isEqualCollection(java.util.Collection a,
java.util.Collection b)
Returns true if the given Collections 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:
the first collection, must not be null
the second
collection, must not be null
Returns:
true if the collections contain the same elements with the same cardinalities.
Related
if I have two lists of objects from Collection interface
list 1 = {John, Tim, Tom}
list 2 = {John, Tim}
and both of the lists are instances of ArrayList
how does Java knows if list2 is contained in list1 with list1.containsall(list2)?
I knows that Java uses contain method inside the implementation of containsall() method, and the contain method uses the equal() method. I understand the differences but I am not sure how Java iterates through the elements of list 1.
so If I use list1.containsAll(list2),, constainsAll() method is implemented with a loop that iterates through every object of in this case, list2, and throws false if one of the elements is not in list 1.
So my main question is how does JAVA know that list 1 contains all of the elements without another loop to iterate through the elements of list 1? Does java does the work internally or something?
I currently know that to do such a thing, I would have to use
for (int i = 0; i < list1.size(), i++)
list1.get(i).constainsAll(list2);
,,
that seems more logical to me taking into consideration that I would have to modified the code for containsAll to work correctly and also implement the method of get()
Maybe to answer this formally because I think it is a good Question.
The containsAll method iterates through the provided collection and performs the contains() method on each entry which also iterates through the other list being compared. See below extract from java code
public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))
return false;
return true;
}
and
public boolean contains(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return true;
} else {
while (it.hasNext())
if (o.equals(it.next()))
return true;
}
return false;
}
This makes this o(n^2) (Worst case scenario if the last values do not match or if the list actually matches) (Which is really bad, especially if you have big collections which you are comparing).
a better approach would be to do something like the following: (Obviously this needs to be adjusted if you are using objects or other collections apart from strings and do some null checks or something)
public boolean containsAllStrings(List<String> list1, List<String> List2) {
Map<String, String> list1Map = list1.stream().collect(Collectors.toMap(c -> c, c -> c));
return List2.stream().allMatch(list1Map::containsKey);
}
This way it Iterates a max number of 2n (one for adding items to map and one for comparing) times (n being the biggest list of the 2) and not n^2.
It may seem the same but hash maps are nice because they contain a pointer to the value in memory (Using the hashed value of the key) and do not iterate overall all the values, making accessing a value in a map always o(1). Which is optimal.
Obviously, there are tradeoffs between approaches like memory utilization, but for speed, this is the best approach.
The official documentation (archive) of containsAll only says "Returns true if this list contains all of the elements of the specified collection.". However, I just tested this:
List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(1);
List<Integer> list2 = new ArrayList<>();
list2.add(2);
list2.add(1);
list2.add(2);
System.out.println(list1.containsAll(list2));
The result is true, even though list1 does not contain a second 2.
So what is the official, completely defined behaviour of containsAll? Does it act as if all duplicates were removed from both lists? I remember reading somewhere that it can cause problems with duplicates, but I don't know the exact case.
The List.containsAll method behaves just as documented: it returns true if all the elements of the given collection belong to this collection, false otherwise. The docs say nothing about the order or cardinality of the elements.
The documentation for containsAll does not explicitly say how it determines whether an element belongs to the Collection. But the documentation for contains (which is implicitly specifying the semantics of "contains") does: it uses equals. Again, no mention of cardinality.
The containsAll method is declared in the Collection interface and re-declared in the List and Set interfaces, but it's first implemented in the Collection hierarchy by the AbstractCollection class, as follows:
public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))
return false;
return true;
}
As far as I know, this implementation is inherited by most common classes that implement the Collection interface in the Java Collections framework, except for the CopyOnWriteArrayList class and other specialized classes, such as empty lists and checked and immutable wrappers, etc.
So, if you look at the code, you'll see that it fulfils the docs you quoted:
Returns true if this list contains all of the elements of the specified collection.
In the docs of the AbstractList.containsAll method, there's also an #implSpec tag, which says the following:
#implSpec
This implementation iterates over the specified collection, checking each element returned by the iterator in turn to see if it's contained in this collection. If all elements are so contained true is returned, otherwise false.
With regard to possible optimizations, they're all relayed to the different implementations of the contains method, which is also implemented by AbstractCollection in a naive, brute-force-like way. However, contains is overriden in i.e. HashSet to take advantage of hashing, and also in ArrayList, where it uses indexes, etc.
You can iterate over one list and remove elements by value from another, then check if another list size == 0. If it is, then that means all second list elements were present in first list at least as many times as in the second list.
public boolean containsAll(List<Character> source, List<Character> target) {
for (Character character : source) {
target.remove(character);
if (target.isEmpty()) {
return true;
}
}
return target.size() == 0;
}
HashMap will be more efficient if lists are huge
public static boolean containsAll(List<Character> source, List<Character> target) {
Map<Character, Long> targetMap = target.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
for (Character character : source) {
Long count = targetMap.get(character);
if (count != null) {
if (count > 1) {
targetMap.put(character, --count);
} else {
targetMap.remove(character);
}
}
}
return targetMap.isEmpty();
}
I have a POJO class SearchResults, that contains 4 Strings (title, number, date, status) and then all the getter and setter methods for it.
In another class I populate an ArrayList<SearchResults> results, is there a way I can go through that list results and erase any elements that have a duplicate number?
I've tried populating a new ArrayList by first passing results into a LinkedHashSet but that didn't work.
ArrayList<SearchResults> noDup;
noDup = new ArrayList<SearchResults>(new LinkedHashSet<SearchResults>(results));
I've also tried doing a .remove(indexof()) but that didn't work either.
if(noDup.contains(new SearchResults("-1","","",""))){noDup.remove(noDup.indexOf(new SearchResults("-1","","","")));}
Any suggestions?
Edit:
The equals() method in SearchResults (wonr refers to the number)
#Override
public boolean equals(Object object){
if(object == null){
return false;
}
if(getClass() != object.getClass()){
return false;
}
SearchResults result = (SearchResults) object;
if((this.wonr == null) ? (result.wonr == null): this.wonr.equals(result.wonr)){
return false;
}
return true;
}
The suggestions for implementing hashCode and equals are possible options, but does this single number value truly define what it means for these objects to be equivalent in the general case? If not, defining equals and hashCode that way seems to be a hack.
Without altering the definition of equivalence, if in just this case you want to elminiate values with the same number value, there are other approaches you can try. You didn't give us the API for your SearchResult class, so I'll assume there's an accessible field named number.
One quick way is to use a TreeSet which defines its idea of equivalence based on an underlying comparison operation. Write a custom Comparator that only looks at the number field and you're good to go:
Java 8
List<SearchResult> allResultsWithDuplicates = // ... populated list
Comparator<SearchResult> comparator =
(left, right) -> Integer.compare(left.number, right.number);
Set<SearchResult> uniqueNumbers = new TreeSet<>(comparator);
uniqueNumbers.addAll(allResultsWithDuplicates);
As JB Nizet mentioned, if your SearchResult class has a getNumber accessor method you can use a function reference and eliminate the lambda expression defining Comparator:
Comparator<SearchReult> comparator = Comparator.comparing(SearchResult::getNumber);
Java 5-7
In earlier versions of Java you must implement the Comparator class yourself. Then it plugs into the code given above in exactly the same way. This example assumes there is a int getNumber() accessor method on your SearchResult class:
Comparator<SearchResult> comparator =
new Comparator<SearchResult>() {
#Override
public int compare(SearchResult sr1, SearchResult sr2) {
// Optional support for null arguments is left as
// an exercise for the reader.
return Integer.compare(sr1.getNumber(), sr2.getNumber());
}
};
Another way you can do it with Java-8 is this way:
1) Create set of unique numbers,
2) Iterate over your list and filter by this set:
Set<Integer> numbers = new HashSet<>();
List<SearchResult> noDups = listWithDups.stream()
.filter(sr -> numbers.add(sr.getNumber()))
.collect(Collectors.toList());
If you implemented equals() and hashCode() so that they just look at Number property you could build a Set<SearchResult> instead of a ArrayList<SearchResult> and you will implicitly get no duplicates (this is one of the properties of sets - they don't contain duplicates). You can still iterate over the entries in the set so you should have all the functionality you need.
Do a stream of our list and use filter method and collect to an other list.
I have a doubt regarding how equals() method works for ArrayList. The below code snippet prints true.
ArrayList<String> s = new ArrayList<String>();
ArrayList<Integer> s1 = new ArrayList<Integer>();
System.out.println(s1.equals(s));
Why does it print true?
Look the doc for the equals() method of ArrayList
Returns true if and only if the specified object is also a list, both lists have the same size, and all corresponding pairs of elements in the two lists are equal.
Since there are no elements, all the conditions satisfied and hence true.
If you add elements to the both list (atleast one in each), to see the desired output.
The contract of the List.equals is that two lists are equal if all their elements are equal (in terms of equals()). Here, both are empty lists, so they are equal. The generic type is irrelevant, as there are anyway no list elements to compare.
However, they are not equal in terms of == as these are two different objects.
See this question for details between equals() and ==
Here is the ArrayList implementation of the equals method from the AbstractList
with a few commments what it actually does:
public boolean equals(Object o) {
if (o == this) // Not the same list so no return
return true;
if (!(o instanceof List)) // is an instance of List, so no return
return false;
ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
while (e1.hasNext() && e2.hasNext()) { // Both have no next, so no loop here
E o1 = e1.next();
Object o2 = e2.next();
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext()); // Both validate to false, so negating false return true in the end.
}
As previous answers pointed, equals returns true because both objects are instances of List and have the same size (0).
It's also worth mentioning that the fact that one List contains Integer and the other String does not affect the behaviour because of type erasure in Java.
How can I check if two ArrayLists differ from one another? I don't care what's the difference, I just want to know if they're not the same.
I'm fetching scores list from a database every minute, and only if the scores list that I fetched is different from the one I fetched a minute ago I want to send it to the client.
Now the value of the ArrayList is actually a class that I created (that contains name, lvl, rank, score).
Do I need to implement equals() on it?
On the definition of "sameness"
As Joachim noted, for most application, List.equals(Object o) definition works:
Compares the specified object with this list for equality. Returns true if and only if the specified object is also a list, both lists have the same size, and all corresponding pairs of elements in the two lists are equal. (Two elements e1 and e2 are equal if (e1==null ? e2==null : e1.equals(e2)).) In other words, two lists are defined to be equal if they contain the same elements in the same order. This definition ensures that the equals method works properly across different implementations of the List interface.
Depending on how you're using it, though, this may not work as expected. If you have a List<int[]>, for example, it doesn't quite work because arrays inherit equals from Object which defines equality as reference identity.
List<int[]> list1 = Arrays.asList(new int[] { 1, 2, 3 });
List<int[]> list2 = Arrays.asList(new int[] { 1, 2, 3 });
System.out.println(list1.equals(list2)); // prints "false"
Also, two lists with different type parameter can be equals:
List<Number> list1 = new ArrayList<Number>();
List<String> list2 = new ArrayList<String>();
System.out.println(list1.equals(list2)); // prints "true"
You also mentioned that the list must contain elements with the same type. Here's yet another example where the elements don't have the same type, and yet they're equals:
List<Object> list1 = new ArrayList<Object>();
List<Object> list2 = new ArrayList<Object>();
list1.add(new ArrayList<Integer>());
list2.add(new LinkedList<String>());
System.out.println(list1.equals(list2)); // prints "true"
So unless you clearly define what equality means to you, the question can have very different answers. For most practical purposes, though, List.equals should suffice.
On implementing equals
Information after update suggests that List.equals will do the job just fine, provided that the elements implement equals properly (because List<E>.equals invokes E.equals on the non-null-elements, per the API documentation above).
So in this case, if we have, say, a List<Player>, then Player must #Override equals(Object o) to return true if o instanceof Player and on the relevant fields, they're all equals (for reference types) or == (for primitives).
Of course, when you #Override equals, you should also #Override int hashCode(). The barely acceptable minimum is to return 42;; slightly better is to return name.hashCode();; best is to use a formula that involves all the fields on which you define equals. A good IDE can automatically generate equals/hashCode methods for you.
See also
Effective Java 2nd Edition
Item 8: Obey the general contract when overriding equals
Item 9: Always override hashcode when you override equals
API links
Object.equals(Object)
Object.hashCode()
java.lang.Comparable - not needed here, but another important Java API contract
Related questions
On equals/hashCode combo:
Overriding equals and hashcode in Java
Why both hashCode() and equals() exist
How to ensure hashCode() is consistent with equals()?
On equals vs ==:
Difference between equals and ==
why equals() method when we have == operator?
Java String.equals versus ==
Use equals(). As long as the elements inside the lists implement equals() correctly it will return the correct values.
Unless you want to ignore the order of the values, then you should dump the values in two Set objects and compare those using equals().
Here's a simple method that checks if 2 Array Lists contain the same values regardless their order.
//the name of the method explains it well...
public boolean isTwoArrayListsWithSameValues(ArrayList<Object> list1, ArrayList<Object> list2)
{
//null checking
if(list1==null && list2==null)
return true;
if((list1 == null && list2 != null) || (list1 != null && list2 == null))
return false;
if(list1.size()!=list2.size())
return false;
for(Object itemList1: list1)
{
if(!list2.contains(itemList1))
return false;
}
return true;
}
As #Joachim Sauer mentioned in his answer, equals should work if the lists are equal and their contents implement equals correctly. But, it shouldn't work if the items are not in the same "order" since it doesn't use contains for the check. In this sense, it checks for "strict" equality as mentioned by #jarnbjo
//From android's Arraylist implementation
Iterator<?> it = that.iterator();
for (int i = 0; i < s; i++) {
Object eThis = a[i];
Object eThat = it.next();
if (eThis == null ? eThat != null : !eThis.equals(eThat)) {
return false;
}
}
However, I wanted somewhat different behaviour, I didn't care about order or anything like that. All I wanted was to be sure the two didn't contain the same items.
My solution,
//first check that both are not null and are of same length. (not shown here)
//if both match, pull out the big guns as below
...
List<Object> comparedList = new ArrayList<>(listOne);
comparedList.removeAll(listTwo);
if(comparedList.size() != 0) //there are differences between the two
This is less performant since it loops twice, first in removeAll and then in contains which is called by removeAll.
My list was guaranteed to be short so I didn't mind the hit.
You can convert them to string and then compare like
list1.toString().equals(list2.toString())
You can also check the Arraylist as shown below:
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);
}
Thanks to #Jacob