Comparing two Lists of a class without iterating the lists - java

I have a class Abc as below
public class Abc {
int[] attributes;
Abc(int[] attributes){
this.attributes = attributes;
}
}
Overriding the Abc hash code as below
#Override
public int hashCode() {
int hashCode = 0;
int multiplier = 1;
for(int i = attributes.length-1 ; i >= 0 ; i++){
hashCode = hashCode+(attributes[i]*multiplier);
multiplier = multiplier*10;
}
return hashCode;
}
I am using above class to create a list of objects and I want to compare whether the two lists are equal i.e. lists having objects with same attributes.
List<Abc> list1 ;
list1.add(new Abc(new int[]{1,2,4}));
list1.add(new Abc(new int[]{5,8,9}));
list1.add(new Abc(new int[]{3,4,2}));
List<Abc> list2;
list2.add(new Abc(new int[]{5,8,9}));
list2.add(new Abc(new int[]{3,4,2}));
list2.add(new Abc(new int[]{1,2,4}));
How can I compare the above two lists with/without iterating over each list . Also is there any better way to override the hashcode , so that two classes having the same attributes(values and order) should be equal.

You have to override the function equals in your class Abc. If you are using an IDE, it can be used to generates something good enough. For example, Eclipse produces the following:
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Abc other = (Abc) obj;
if (!Arrays.equals(attributes, other.attributes)) {
return false;
}
return true;
}
With this equals method, you can now check that two instance of Abc are equal.
If you want to compare your two lists list1 and list2, unfortunately you can not simply do
boolean listsAreEqual = list1.equals(list2); // will be false
because that would not only check if the elements in the lists are the same but also if they are in the same order. What you can do is to compare two sets, because in sets, the elements have no order.
boolean setAreEqual = new HashSet<Abc>(list1).equals(new HashSet<Abc>(list2)); // will be true.
Note that in that case, you should keep your implementation of hashcode() in Abc, for the HashSet to function well. As a general rule, a class that implements equals should also implement hashcode.
The problem with a Set (HashSet are Set) is that by design it will not contain several objects which are equal with each other. Objects are guaranteed to be unique in a set. For example, if you add a new new Abc(new int[]{5,8,9}) in the second set, the two sets will still be equal.
If it bothers you then the possible solution is either to compare two lists, but after having sorted them beforehand (for that you have to provide a comparator or implements compareTo), or use Guava's HashMultiset, which is an unordered container that can contain the same objects multiple times.

Override the equals method to compare objects. As the comments mention, you should be overriding the hashcode method as well when overriding equals method.
By this
so that two classes having the same attributes(values and order) should be equal.
i think you mean two objects having same attributes.
you can try something like this
public boolean equals(Object o) {
if(!(Object instanceOf Abc)) {
return false;
}
Abc instance = (Abc)o;
int[] array = instance.attributes;
for(i=0;i<array.length;i++){
if(array[i]!=this.attributes[i]) {
return false;
}
}
}
Edit: As for the hashcode the concept is that when
object1.equals(object2)
is true, then
object1.hashcode()
and
object2.hashcode()
must return the same value. and hashCode() of an object should be same and consistent through the entire lifetime of it. so generating hashcode based on the value of its instance variables is not a good option as a different hashcode may be generated when the instance variable value changes.

Related

Java Set removes "complex object"

I'm always confused about the Java Collections (set, map) remove "complex object", which I mean some self-defined class rather than just primitive type.
I'm experimenting like:
public class Main {
public static void main(String[] args) {
// set
Set<Node> set = new HashSet<>();
set.add(new Node(1,2));
set.add(new Node(3,4));
System.out.println(set);
set.remove(new Node(1,2));
System.out.println(set + "\n");
// tree set
TreeSet<Node> tset = new TreeSet<>((a, b) -> a.name - b.name);
tset.add(new Node(1,2));
tset.add(new Node(3,4));
System.out.println(tset);
tset.remove(new Node(1,2));
System.out.println(tset);
}
}
class Node {
int name;
int price;
Node(int name, int price) {
this.name = name;
this.price = price;
}
}
In the example above, the printout would be:
Set:
[Node#5ba23b66, Node#2ff4f00f]
[Node#5ba23b66, Node#2ff4f00f]
TreeSet:
[Node#48140564, Node#58ceff1]
[Node#58ceff1]
Obviously, the general Set can't remove new Node(1, 2), which is treated as a different object. But interestingly the TreeSet can remove, which I think because the hashing code is based on the lambda comparator I defined here?
And if I change to remove new Node(1, 6), interestingly it's the same printout, where obviously the remove in TreeSet is based on only the name value.
I think I still lack of deep understanding of how Set build up hashing and how comparator would affect this.
For HashMap and HashSet, you need to overwrite hashCode() and equals(Object), where if two objects are equal, they should have equal hash codes. E.g., in your case, you could implement it like this:
#Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
Node node = (Node) o;
return name == node.name && price == node.price;
}
#Override
public int hashCode() {
return Objects.hash(name, price);
}
For TreeMap and TreeSet, the notion of equality is based on a comparison (whether the class implements Comparable, or you supply a custom Comparator). In the code you provided, you have a custom Comparator that only takes the name in to consideration, so it would consider any two Nodes with the same name as being equal, regardless of their price.
javadoc comes to rescue
https://docs.oracle.com/javase/7/docs/api/java/util/Set.html#remove(java.lang.Object)
Removes the specified element from this set if it is present (optional
operation). More formally, removes an element e such that (o==null ?
e==null : o.equals(e)), if this set contains such an element. Returns
true if this set contained the element (or equivalently, if this set
changed as a result of the call). (This set will not contain the
element once the call returns.)
So just change your Node class and override equals(Object o) method with your own.

Check if custom List contains an Item

I've got an custom List and want to check if it contains a special Item. TheList is populated with Rowlayout objects.
public RowLayout(String content, int number) {
this.content = content;
this.number = number;
}
Now i wanna check if my List<Roalayout> contains a special item at the content - position. How do I do that?
It doesn't work with just asking .contains'.
What i wanna check:
if (!List<RowLayout>.contains("insert here"){
//Do something
}
If you can edit the class RowLayout just override hashCode and equals with whatever equality you want for them.
If you can't and have java-8 for example, this could be done:
String content = ...
int number = ...
boolean isContained = yourList.stream()
.filter(x -> x.getContent().equals(content))
.filter(x -> x.getNumber() == number)
.findAny()
.isPresent();
You can obviously return the instance you are interested in from that Optional from findAny.
You just need to override equals for List.contains to work accordingly. List.contains says in the documentation:
Returns true if and only if this list contains at least one element e
such that(o==null ? e==null : o.equals(e)).
Your implementation of equals may look like this:
class RowLayout {
private String content;
private int number;
public boolean equals(Object o)
{
if (!(o instanceof RowLayout)) return false;
final RowLayout that = (RowLayout) o;
return this.content.equals(that.content) && this.number == that.number;
}
}
Don't forget to also override hashCode, else your class will not work in hash-based structures like HashSets or HashMaps.
Example usage:
myList.contains(new RowLayout("Hello", 99));
An alternative Java 8 solution if you only care about the content and don't care about the number would be to do this:
boolean isContained = myList.stream()
.map(RowLayout::getContent)
.anyMatch("some content");

Element is present but `Set.contains(element)` returns false

How can an element not be contained in the original set but in its unmodified copy?
The original set does not contain the element while its copy does. See image.
The following method returns true, although it should always return false. The implementation of c and clusters is in both cases HashSet.
public static boolean confumbled(Set<String> c, Set<Set<String>> clusters) {
return (!clusters.contains(c) && new HashSet<>(clusters).contains(c));
}
Debugging has shown that the element is contained in the original, but Set.contains(element) returns false for some reason. See image.
Could somebody please explain to me what's going on?
If you change an element in the Set (in your case the elements are Set<String>, so adding or removing a String will change them), Set.contains(element) may fail to locate it, since the hashCode of the element will be different than what it was when the element was first added to the HashSet.
When you create a new HashSet containing the elements of the original one, the elements are added based on their current hashCode, so Set.contains(element) will return true for the new HashSet.
You should avoid putting mutable instances in a HashSet (or using them as keys in a HashMap), and if you can't avoid it, make sure you remove the element before you mutate it and re-add it afterwards. Otherwise your HashSet will be broken.
An example :
Set<String> set = new HashSet<String>();
set.add("one");
set.add("two");
Set<Set<String>> setOfSets = new HashSet<Set<String>>();
setOfSets.add(set);
boolean found = setOfSets.contains(set); // returns true
set.add("three");
Set<Set<String>> newSetOfSets = new HashSet<Set<String>>(setOfSets);
found = setOfSets.contains(set); // returns false
found = newSetOfSets.contains(set); // returns true
The most common reason for this is that the element or key was altered after insertion resulting in a corruption of the underlying data structure.
note: when you add a reference to a Set<String> to another Set<Set<String>> you are adding a copy of the reference, the underlyingSet<String> is not copied and if you alter it these changes which affect the Set<Set<String>> you put it into.
e.g.
Set<String> s = new HashSet<>();
Set<Set<String>> ss = new HashSet<>();
ss.add(s);
assert ss.contains(s);
// altering the set after adding it corrupts the HashSet
s.add("Hi");
// there is a small chance it may still find it.
assert !ss.contains(s);
// build a correct structure by copying it.
Set<Set<String>> ss2 = new HashSet<>(ss);
assert ss2.contains(s);
s.add("There");
// not again.
assert !ss2.contains(s);
If the primary Set was a TreeSet (or perhaps some other NavigableSet) then it is possible, if your objects are imperfectly compared, for this to happen.
The critical point is that HashSet.contains looks like:
public boolean contains(Object o) {
return map.containsKey(o);
}
and map is a HashMap and HashMap.containsKey looks like:
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
so it uses the hashCode of the key to check for presence.
A TreeSet however uses a TreeMap internally and it's containsKey looks like:
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
...
So it uses a Comparator to find the key.
So, in summary, if your hashCode method does not agree with your Comparator.compareTo method (say compareTo returns 1 while hashCode returns different values) then you will see this kind of obscure behaviour.
class BadThing {
final int hash;
public BadThing(int hash) {
this.hash = hash;
}
#Override
public int hashCode() {
return hash;
}
#Override
public String toString() {
return "BadThing{" + "hash=" + hash + '}';
}
}
public void test() {
Set<BadThing> primarySet = new TreeSet<>(new Comparator<BadThing>() {
#Override
public int compare(BadThing o1, BadThing o2) {
return 1;
}
});
// Make the things.
BadThing bt1 = new BadThing(1);
primarySet.add(bt1);
BadThing bt2 = new BadThing(2);
primarySet.add(bt2);
// Make the secondary set.
Set<BadThing> secondarySet = new HashSet<>(primarySet);
// Have a poke around.
test(primarySet, bt1);
test(primarySet, bt2);
test(secondarySet, bt1);
test(secondarySet, bt2);
}
private void test(Set<BadThing> set, BadThing thing) {
System.out.println(thing + " " + (set.contains(thing) ? "is" : "NOT") + " in <" + set.getClass().getSimpleName() + ">" + set);
}
prints
BadThing{hash=1} NOT in <TreeSet>[BadThing{hash=1}, BadThing{hash=2}]
BadThing{hash=2} NOT in <TreeSet>[BadThing{hash=1}, BadThing{hash=2}]
BadThing{hash=1} is in <HashSet>[BadThing{hash=1}, BadThing{hash=2}]
BadThing{hash=2} is in <HashSet>[BadThing{hash=1}, BadThing{hash=2}]
so even though the object is in the TreeSet it is not finding it because the comparator never returns 0. However, once it is in the HashSet all is fine because HashSet uses hashCode to find it and they behave in a valid way.

Remove duplicates by certain key values

Say I have my object
class MyObject{
private int id;
private int secondId;
private String name;
private String address;
}
And I'm adding lists of these objects to a list.
List<MyObject> finalList = new ArrayList<MyObject>();
while(someCondition) {
List<MyObject> l = getSomeMoreObjects();
finalList.addAll(l);
}
This is all well and good, except I only want to add the new records to the list if they have a distinct id and secondId.
What would the best way to do this? I'm thinking it would involve using a HashMap.
You'll want to override the hashCode and equals methods in MyObject:
#Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + this.id;
hash = 97 * hash + this.secondId;
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof MyObject))
return false;
MyObject other = (MyObject) obj;
return this.id == other.id && this.secondId == other.secondId;
}
Then create the HashSet:
HashSet<MyObject> set = new HashSet<>();
Then just add objects to it:
set.add(new MyObject());
The HashSet will ignore your new object if you already have one with the same id and secondId in the set.
Override the equals method in MyObject (two object are equal if and only if they have the same id and second id), and use a HashSet for storing distinct values.
Here is how you override the method:
Override equals method
You may use HashSet. For using HashSet -
override you MyObject equals() method and hashCode().
add l to you HashSet

A TreeSet or TreeMap that allow duplicates

I need a Collection that sorts the element, but does not removes the duplicates.
I have gone for a TreeSet, since TreeSet actually adds the values to a backed TreeMap:
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
And the TreeMap removes the duplicates using the Comparators compare logic
I have written a Comparator that returns 1 instead of 0 in case of equal elements. Hence in the case of equal elements the TreeSet with this Comparator will not overwrite the duplicate and will just sort it.
I have tested it for simple String objects, but I need a Set of Custom objects.
public static void main(String[] args)
{
List<String> strList = Arrays.asList( new String[]{"d","b","c","z","s","b","d","a"} );
Set<String> strSet = new TreeSet<String>(new StringComparator());
strSet.addAll(strList);
System.out.println(strSet);
}
class StringComparator implements Comparator<String>
{
#Override
public int compare(String s1, String s2)
{
if(s1.compareTo(s2) == 0){
return 1;
}
else{
return s1.compareTo(s2);
}
}
}
Is this approach fine or is there a better way to achieve this?
EDIT
Actually I am having a ArrayList of the following class:
class Fund
{
String fundCode;
BigDecimal fundValue;
.....
public boolean equals(Object obj) {
// uses fundCode for equality
}
}
I need all the fundCode with highest fundValue
You can use a PriorityQueue.
PriorityQueue<Integer> pQueue = new PriorityQueue<Integer>();
PriorityQueue(): Creates a PriorityQueue with the default initial capacity (11) that orders its elements according to their natural ordering.
This is a link to doc: https://docs.oracle.com/javase/8/docs/api/java/util/PriorityQueue.html
I need all the fundCode with highest fundValue
If that's the only reason why you want to sort I would recommend not to sort at all. Sorting comes mostly with a complexity of O(n log(n)). Finding the maximum has only a complexity of O(n) and is implemented in a simple iteration over your list:
List<Fund> maxFunds = new ArrayList<Fund>();
int max = 0;
for (Fund fund : funds) {
if (fund.getFundValue() > max) {
maxFunds.clear();
max = fund.getFundValue();
}
if (fund.getFundValue() == max) {
maxFunds.add(fund);
}
}
You can avoid that code by using a third level library like Guava. See: How to get max() element from List in Guava
you can sort a List using Collections.sort.
given your Fund:
List<Fund> sortMe = new ArrayList(...);
Collections.sort(sortMe, new Comparator<Fund>() {
#Override
public int compare(Fund left, Fund right) {
return left.fundValue.compareTo(right.fundValue);
}
});
// sortMe is now sorted
In case of TreeSet either Comparator or Comparable is used to compare and store objects . Equals are not called and that is why it does not recognize the duplicate one
Instead of the TreeSet we can use List and implement the Comparable interface.
public class Fund implements Comparable<Fund> {
String fundCode;
int fundValue;
public Fund(String fundCode, int fundValue) {
super();
this.fundCode = fundCode;
this.fundValue = fundValue;
}
public String getFundCode() {
return fundCode;
}
public void setFundCode(String fundCode) {
this.fundCode = fundCode;
}
public int getFundValue() {
return fundValue;
}
public void setFundValue(int fundValue) {
this.fundValue = fundValue;
}
public int compareTo(Fund compareFund) {
int compare = ((Fund) compareFund).getFundValue();
return compare - this.fundValue;
}
public static void main(String args[]){
List<Fund> funds = new ArrayList<Fund>();
Fund fund1 = new Fund("a",100);
Fund fund2 = new Fund("b",20);
Fund fund3 = new Fund("c",70);
Fund fund4 = new Fund("a",100);
funds.add(fund1);
funds.add(fund2);
funds.add(fund3);
funds.add(fund4);
Collections.sort(funds);
for(Fund fund : funds){
System.out.println("Fund code: " + fund.getFundCode() + " Fund value : " + fund.getFundValue());
}
}
}
Add the elements to the arraylist and then sort the elements using utility Collections.sort,. then implement comparable and write your own compareTo method according to your key.
wont remove duplicates as well, can be sorted also:
List<Integer> list = new ArrayList<>();
Collections.sort(list,new Comparator<Integer>()
{
#Override
public int compare(Objectleft, Object right) {
**your logic**
return '';
}
}
)
;
I found a way to get TreeSet to store duplicate keys.
The problem originated when I wrote some code in python using SortedContainers. I have a spatial index of objects where I want to find all objects between a start/end longitude.
The longitudes could be duplicates but I still need the ability to efficiently add/remove specific objects from the index. Unfortunately I could not find the Java equivalent of the Python SortedKeyList that separates the sort key from the type being stored.
To illustrate this consider that we have a large list of retail purchases and we want to get all purchases where the cost is in a specific range.
// We are using TreeSet as a SortedList
TreeSet _index = new TreeSet<PriceBase>()
// populate the index with the purchases.
// Note that 2 of these have the same cost
_index.add(new Purchase("candy", 1.03));
Purchase _bananas = new Purchase("bananas", 1.45);
_index.add(new Purchase(_bananas);
_index.add(new Purchase("celery", 1.45));
_index.add(new Purchase("chicken", 4.99));
// Range scan. This iterator should return "candy", "bananas", "celery"
NavigableSet<PriceBase> _iterator = _index.subset(
new PriceKey(0.99), new PriceKey(3.99));
// we can also remove specific items from the list and
// it finds the specific object even through the sort
// key is the same
_index.remove(_bananas);
There are 3 classes created for the list
PriceBase: Base class that returns the sort key (the price).
Purchase: subclass that contains transaction data.
PriceKey: subclass used for the range search.
When I initially implemented this with TreeSet it worked except in the case where the prices are the same. The trick is to define the compareTo() so that it is polymorphic:
If we are comparing Purchase to PriceKey then only compare the price.
If we are comparing Purchase to Purchase then compare the price and the name if the prices are the same.
For example here are the compareTo() functions for the PriceBase and Purchase classes.
// in PriceBase
#Override
public int compareTo(PriceBase _other) {
return Double.compare(this.getPrice(), _other.getPrice());
}
// in Purchase
#Override
public int compareTo(PriceBase _other) {
// compare by price
int _compare = super.compareTo(_other);
if(_compare != 0) {
// prices are not equal
return _compare;
}
if(_other instanceof Purchase == false) {
throw new RuntimeException("Right compare must be a Purchase");
}
// compare by item name
Purchase _otherPurchase = (Purchase)_other;
return this.getName().compareTo(_otherChild.getName());
}
This trick allows the TreeSet to sort the purchases by price but still do a real comparison when one needs to be uniquely identified.
In summary I needed an object index to support a range scan where the key is a continuous value like double and add/remove is efficient.
I understand there are many other ways to solve this problem but I wanted to avoid writing my own tree class. My solution seems like a hack and I am surprised that I can't find anything else. if you know of a better way then please comment.

Categories