implement comparable interface for bidirectional flows - java

this piece of code is supposed to consider flows in both direction as one flow.
for example:
srcAddr,dstAddr,srcPort,dstPort
192.168.1.65, 217.174.16.1, 123456,80
should be the same as
217.174.16.1, 192.168.1.65,80,123456
Another Example:
192.168.1.65, 217.174.16.1, 12345, 80, TCP
217.174.16.1, 192.168.1.65, 80, 12345, TCP
192.168.1.65, 217.174.16.1, 12345, 80, TCP
217.174.16.1, 192.168.1.65, 80, 12345, TCP
I want to keep i t like this:
Flow 1: key---> value (keeps statistics about each packet, like length and timeArrival)
[192.168.1.65, 217.174.16.1, 12345, 80] ----> [(outgoing, 1,2)(incoming,3,4)()()...]
192.168.1.65, 69.100.70.80, 98521, 80
69.100.70.80, 192.168.1.65, 80, 98521
192.168.1.65, 69.100.70.80, 98521, 80
69.100.70.80, 192.168.1.65, 80, 98521
192.168.1.65, 69.100.70.80, 98521, 80
69.100.70.80, 192.168.1.65, 80, 98521
Flow 2: [192.168.1.65, 69.100.70.80, 98521, 80] --> [(outgoing, 1,2)(incoming,3,4)()()...]
how should i change it in order to get the result?
[im using a hashMap and this class of Flows are my keys]
package myclassifier;
public class Flows implements Comparable<Flows> {
String srcAddr = "", dstAddr = "", protocol = "";
int srcPort = 0, dstPort = 0;
public Flows(String sIP, String dIP, int sPort, int dPort){
this.srcAddr = sIP;
this.dstAddr = dIP;
this.srcPort = sPort;
this.dstPort = dPort;
//this.protocol = protocol;
}
public Flows(){
}
public int compareTo(Flows other) {
int res = 1;
if(this.equals(other)){
return res=0;
}else
return 1;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((dstAddr == null) ? 0 : dstAddr.hashCode());
result = prime * result + dstPort;
result = prime * result + ((srcAddr == null) ? 0 : srcAddr.hashCode());
result = prime * result + srcPort;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Flows other = (Flows) obj;
if (dstAddr == null) {
if (other.dstAddr != null)
return false;
} else if (!dstAddr.equals(other.dstAddr))
return false;
if (dstPort != other.dstPort)
return false;
if (srcAddr == null) {
if (other.srcAddr != null)
return false;
} else if (!srcAddr.equals(other.srcAddr))
return false;
if (srcPort != other.srcPort)
return false;
return true;
}
#Override
public String toString() {
return String.format("[%s, %s, %s, %s, %s]", srcAddr, dstAddr, srcPort, dstPort, protocol);
}
}

Probably the cleanest way to do this is to define these methods:
Flows reverse() that returns the reversed direction Flows of a given Flows
Flows canon() which returns a canonicalized form of a Flows
You can define e.g. a Flows is canon if srcAddr.compareTo(dstAddr) <= 0
Otherwise, its reverse() is canon by definition
Then for non-directional comparison, you can simply compare the canonical forms of the two flows. Having these methods makes the rest of the logic very clean and readable (see code below).
On Comparator, Comparable, and consistency with equals
Using the reverse() concept above, if you want f.equals(f.reverse()) always, then perhaps there shouldn't be any concept of directionality in the first place. If this is the case, then canonicalization is the best approach.
If f is generally not equals(f.reverse()), and yet you may want f and f.reverse() to compare to 0, then Comparable should not be used, because doing so would impose a natural ordering that is not consistent with equals.
From the documentation:
The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C.
It is strongly recommended (though not required) that natural orderings be consistent with equals.
That is, instead of imposing a natural ordering in Comparable that is inconsistent with equals, you should instead provide a non-directional Comparator instead.
As an analogy, compare this situation with String, which provides Comparator<String> CASE_INSENSITIVE_ORDER, which allows two strings that are not equals to compare to 0 by case-insensitivity.
So here you'd write a Comparator<Flows> that allows two Flows that are not equals to compare to 0 by directional-insensitivity.
See also
Java Tutorials/Collections/Object Ordering
Related questions
When to use Comparable vs Comparator
Java: What is the difference between implementing Comparable and Comparator?
difference between compare() and compareTo()
Comparable and Comparator contract with regards to null
Why does the Java Collections Framework offer two different ways to sort?
Example implementation
Here's an example implementation of an Edge class that has a from and to, with a directional natural ordering that is consistent with equals, which also provides a non-directional Comparator.
It's then tested with 3 kinds of Set:
A HashSet, to test equals and hashCode
A TreeSet, to test natural ordering
A TreeSet with the custom Comparator, to test non-directionality
The implementation is concise and clear, and should be instructive.
import java.util.*;
class Edge implements Comparable<Edge> {
final String from, to;
public Edge(String from, String to) {
this.from = from;
this.to = to;
}
#Override public String toString() {
return String.format("%s->%s", from, to);
}
public Edge reverse() {
return new Edge(to, from);
}
public Edge canon() {
return (from.compareTo(to) <= 0) ? this : this.reverse();
}
#Override public int hashCode() {
return Arrays.hashCode(new Object[] {
from, to
});
}
#Override public boolean equals(Object o) {
return (o instanceof Edge) && (this.compareTo((Edge) o) == 0);
}
#Override public int compareTo(Edge other) {
int v;
v = from.compareTo(other.from);
if (v != 0) return v;
v = to.compareTo(other.to);
if (v != 0) return v;
return 0;
}
public static Comparator<Edge> NON_DIRECTIONAL =
new Comparator<Edge>() {
#Override public int compare(Edge e1, Edge e2) {
return e1.canon().compareTo(e2.canon());
}
};
}
public class Main {
public static void main(String[] args) {
testWith(new HashSet<Edge>());
testWith(new TreeSet<Edge>());
testWith(new TreeSet<Edge>(Edge.NON_DIRECTIONAL));
}
public static void testWith(Set<Edge> set) {
set.clear();
set.add(new Edge("A", "B"));
set.add(new Edge("C", "D"));
System.out.println(set.contains(new Edge("A", "B")));
System.out.println(set.contains(new Edge("B", "A")));
System.out.println(set.contains(new Edge("X", "Y")));
System.out.println(set);
set.add(new Edge("B", "A"));
set.add(new Edge("Z", "A"));
System.out.println(set);
System.out.println();
}
}
The output is (as seen on ideone.com) below, annotated:
// HashSet
// add(A->B), add(C->D)
true // has A->B?
false // has B->A?
false // has X->Y?
[C->D, A->B]
// add(B->A), add(Z->A)
[B->A, C->D, Z->A, A->B]
// TreeSet, natural ordering (directional)
// add(A->B), add(C->D)
true // has A->B?
false // has B->A?
false // has X->Y
[A->B, C->D]
// add(B->A), add(Z->A)
[A->B, B->A, C->D, Z->A]
// TreeSet, custom comparator (non-directional)
// add(A->B), add(C->D)
true // has A->B?
true // has B->A?
false // has X->Y?
[A->B, C->D]
// add(B->A), add(Z->A)
[A->B, Z->A, C->D]
Note that in the non-directional TreeSet, Z->A is canonicalized to A->Z, which is why it appears before C->D in this order. Similarly, B->A is canonicalized to A->B, which is already in the set, which explains why there are only 3 Edge there.
Key points
Edge is immutable
Arrays.hashCode(Object[]) is used for convenience; no need to code all that formulas
If the natural ordering is consistent with equals, you can use compareTo == 0 in equals
Use the multistep return logic in compareTo for conciseness and clarity
Having reverse() and canon() greatly simplifies the non-directional comparison
Simply compare their canonicalized forms in their natural ordering
See also
Effective Java 2nd Edition
Item 8: Obey the general contract when overriding equals
Item 9: Always override hashCode when you override equals
Item 10: Always override toString
Item 12: Consider implementing Comparable
Item 15: Minimize mutability
Item 36: Consistently use #Override annotation
Item 47: Know and use libraries

The key is to have a correct implementation of equals method. In your equals method, you are returning false, the moment destination addresses mismatch. This is where you need to add additional logic to check for equality since you want to have bi-directional equality. The first pass of equality should be a check of equality of source, destination, port. The second pass should be reverse equality of source and destination. You also need to have special provision for default values, like in your example, an exclusion of portno(80) defaults to true.

I don't know if this will help you.But it works in both the directions as you say
import java.util.HashSet;
public class Flows implements Comparable<Flows> {
String srcAddr = "", dstAddr = "", protocol = "";
int srcPort = 0, dstPort = 0;
public Flows(String sIP, String dIP, int sPort, int dPort){
this.srcAddr = sIP;
this.dstAddr = dIP;
this.srcPort = sPort;
this.dstPort = dPort;
//this.protocol = protocol;
}
public Flows(){
}
public int compareTo(Flows other) {
if(this.equals(other)){
return 0;
}else
return 1;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((dstAddr == null) ? 0 : dstAddr.hashCode())+((srcAddr == null) ? 0 : srcAddr.hashCode());
result = prime * result + dstPort+srcPort;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if(obj instanceof Flows)
{
Flows c=(Flows)obj;
if(srcAddr.equals(c.dstAddr) && dstAddr.equals(c.srcAddr) &&srcPort==c.dstPort && dstPort==c.srcPort)
return true;
if(srcAddr.equals(c.srcAddr) && dstAddr.equals(c.dstAddr) && srcPort==c.srcPort && dstPort==c.dstPort)
return true;
}
return false;
}
#Override
public String toString() {
return String.format("[%s, %s, %s, %s, %s]", srcAddr, dstAddr, srcPort, dstPort, protocol);
}
public static void main(String[] args) {
Flows f1=new Flows("192.168.1.65","217.174.16.1", 123456,80);
Flows f2=new Flows("217.174.16.1","192.168.1.65",80,123456);
Flows f3=new Flows("192.168.1.66","217.174.16.1", 123456,80);
Flows f4=new Flows("217.174.16.1","192.168.1.66",80, 123456);
System.out.println(f1.hashCode()+ " "+f2.hashCode());
HashSet<Flows> hh=new HashSet<Flows>();
hh.add(f1);
hh.add(f2);
hh.add(f3);
hh.add(f4);
System.out.println(f1.compareTo(f2));
System.out.println(hh);
}
}
I have used hashset to test.So it should work fine for hashmap too.

Related

How to sort sortedset by value that can be duplicate?

In Java 1.7, I have a "Post class" that has the Post ID and the number of votes of every Post. I want to create sorted set of Posts that can be always sorted by the number of votes. Please be informed that different Posts can have the same number of votes.
The problem is that when I create 2 different Posts with 2 different IDs and different number of votes, the sorted set detects that they are different Posts and thus add them twice instead of replacing the existing thread with the number of new votes. The example below
Post Class:
public class Post implements Comparable<Post> {
protected int id;
protected int votes;
public Post(int id) {
this.id = id;
this.votes = 0;
}
public Post(int id, int votes) {
this.id = id;
this.votes = votes;
}
#Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
Post post= (Post) o;
return id == employee.id;
}
#Override
public int hashCode() {
return Objects.hash(this.id);
}
#Override
public int compareTo(Post t) {
int diff = ((Integer) t.votes).compareTo(this.votes);
if (diff == 0) {
return ((Integer) t.id).compareTo(this.id);
}
return diff;
}
}
Run Method:
public void run() {
SortedSet<Post> set = new TreeSet<Post>();
Post t1 = new Post(1, 30);
Post t2 = new Post(1, 40);
Post t3 = new Post(2, 100);
set.add(t1);
set.add(t2);
set.add(t3);
for (Post t : set) {
System.err.println(t.id + " >> " + t.votes);
}
}
Expected Output:
2 >> 100
1 >> 40
Actual Output
2 >> 100
1 >> 40
1 >> 30
As you can see the problem is that the same Post appeared twice in the set which is not the desired output.
I also tried to avoid using Comparable interface and instead I used Comparator, yet, I got the same result.
Comparator Class:
class CompareByVotes implements Comparator<Post> {
#Override
public int compare(Post t1, Post t2) {
int diff = ((Integer) t2.votes).compareTo(t1.votes);
if (diff == 0) {
return ((Integer) t2.id).compareTo(t1.id);
}
return diff;
}
}
Question:
Any changes required to get it work as desired ?
Your compareTo() method doesn't return 0 when the objects you compare are equal based on the equals() method. However, this is required by the SortedSet interface:
Note that the ordering maintained by a sorted set (whether or not an explicit comparator is provided) must be consistent with equals if the sorted set is to correctly implement the Set interface. (See the Comparable interface or Comparator interface for a precise definition of consistent with equals.) This is so because the Set interface is defined in terms of the equals operation, but a sorted set performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the sorted set, equal. The behavior of a sorted set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.
So your compareTo() method must return 0 when they are equal. One possible solution would be something like this:
public int compareTo(Post t) {
if (equals(t)) {
return 0;
}
int diff = ((Integer) t.votes).compareTo(this.votes);
if (diff == 0) {
return ((Integer) t.id).compareTo(this.id);
}
return diff;
}
Also, keep in mind that add() does not "overwrite" the object, when an equal object is already in the set. See the documentation of add():
[...] If this set already contains the element, the call leaves the set unchanged and returns false.

How to override the hashcode method according to this special equals logic

We have a special logic for a "equals" method, like below:
We use idTypeA/idTypeB/idTypeC as key to compare, only if they are not empty for both objects. How to correctly override the hashCode method, accordingly this kind of equals logic ?
public class Student {
private String idTypeA;
private String idTypeB;
private String idTypeC;
public Student(String idTypeA, String idTypeB, String idTypeC) {
this.idTypeA = idTypeA;
this.idTypeB = idTypeB;
this.idTypeC = idTypeC;
}
#Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Student)){
return false;
}
Student keyIn = (Student) obj;
if ((!idTypeA.isEmpty()) && (!keyIn.idTypeA.isEmpty())) {
if (0 == idTypeA.compareToIgnoreCase(keyIn.idTypeA)) {
return true;
} else {
return false;
}
}
if ((!idTypeB.isEmpty()) && (!keyIn.idTypeB.isEmpty())) {
if (0 == idTypeB.compareToIgnoreCase(keyIn.idTypeB)) {
return true;
} else {
return false;
}
}
if ((!idTypeC.isEmpty()) && (!keyIn.idTypeC.isEmpty())) {
if (0 == idTypeC.compareToIgnoreCase(keyIn.idTypeC)) {
return true;
} else {
return false;
}
}
return false;
}
#Override
public int hashCode() {
// TODO ??? How to correctly override the hashCode method
// according to above equals method
return super.hashCode();
}
}
Thanks,
Frank
This equals method isn't transitive:
("a", "", "c") is equal to ("a", "b", "c")
("a", "b", "c") is equal to ("", "b", "d")
But ("a", "", "c") isn't equal to ("", "b", "d")
This means that your equals method doesn't meet the requirements on overriding equals().
It's fine to have application-specific notions of "equality" (a reasonably common one is "almost equal to"). But you just can't shoe-horn these into Java's specific notions of equals() (and hashCode) because code relying on the documented properties of equals (reflexivity, symmetry, transitivity etc) won't behave as expected.
As such, it doesn't really make sense to ask how to implement hashCode(), because it can't meet the requirements of that either.
The way I see it you have two basic options:
Return 0 if one of the keys is empty, and a regular hash code based on the key values otherwise.
This might be inefficient if you expect to have a lot of empty keys, as most lookups will have to defer to equals() to verify that a match has been found.
Return a random hash code if one of the keys is empty, and a regular hash code based on the key values otherwise.
This is inefficient because generating random values is expensive in terms of processing, but lookups will be fast.
So it's a bit of a catch 22. Both approaches are valid and don't violate the equals()/hashCode() contract, but they come with different performance trade-offs.
Based on your original question and the comments discussed under it and Robby's answer, I would reformulate your question as follows: How to manage a set of objects having 3 different keys used for equality matching, in an optimal way? So I will be answering to this question, in case someone will blame for not answering to the original one.
My suggestion is to make a wrapper class to hold 3 hash-maps and compare your Student objects with those 3 String keys with its help.
Here's my implementation, with an assumption that your keys are public fields instead of being private as mentioned in your original post.
import java.util.HashMap;
import java.util.List;
public class StudentX {
private HashMap<String, Student> map1 = new HashMap();
private HashMap<String, Student> map2 = new HashMap();
private HashMap<String, Student> map3 = new HashMap();
public void addStudent(Student s) {
map1.put(s.idTypeA, s);
map2.put(s.idTypeB, s);
map3.put(s.idTypeC, s);
}
public void bulkAddStudents(List<Student> allStudents) {
for (Student s : allStudents) {
addStudent(s);
}
}
public Student findStudent(Student s) {
Student result;
result = map1.get(s.idTypeA);
if (result != null) {
return result;
}
result = map2.get(s.idTypeB);
if (result != null) {
return result;
}
result = map3.get(s.idTypeC);
if (result != null) {
return result;
}
return null;
}
}
and here's some code for testing it, or to understand how I imagined using it:
List<Student> students = new LinkedList();
students.add(new Student("spades", "hearts", "diamonds"));
students.add(new Student("hearts", "diamonds", "clubs"));
students.add(new Student("spades", "diamonds", "hearts"));
StudentX group = new StudentX();
group.bulkAddStudents(students);
Student noob = new Student("diamonds", "clubs", "hearts"); //try different values for proper testing
if (group.findStudent(noob) != null) {
System.out.println("Student found");
} else {
System.out.println("Student not found");
}
Hope this was clear what I meant with a wrapper class to hold 3 hash-maps for optimal searching and comparison.

Need proper hashCode when comparing Object with unordered pair of integers as variables

I have a class
final class BuildingPair {
int mBA;
int mBB;
public BuildingPair(int pBuildingA,int pBuildingB) {
mBA = pBuildingA;
mBB = pBuildingB;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + mBA;
result = prime * result + mBB;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BuildingPair other = (BuildingPair) obj;
if ((mBA==other.mBA&&mBB==other.mBB)||(mBA==other.mBB&&mBB==other.mBA)) return true;
return false;
}
}
I want to compare two objects , and when both have the same buildings ids they are equal
so they need to be equal in both directions when :
BuildingPair(1,2) vs BuildingPair(2,1)
BuildingPair(1,2) vs BuildingPair(1,2)
BuildingPair(2,1) vs BuildingPair(1,2)
i think equals method is ok, but hashcode is wrong.
You need something that computes the same result whether passed A,B or B,A. There may be far more subtle solutions, but I'd probably just go for:
#Override
public int hashCode() {
return mBA * mBB;
}
Or anything else which uses an operator that is commutative.
Alternatively, you could change your constructor so that it always stores min(a,b) in mBA and max(a,b) in mBB - you can then simplify your comparison code and keep your hash code as it currently is.
You need a symmetric hashcode (hashcode(a,b) == hashcode(b,a)), for example:
return mBB ^ mBA;
(your current code is not symmetric - for example: hascode (2,1) = 1024 but hashcode(1,2) = 994)
Note: this is inspired from the hashcode of Long:
return (int)(value ^ (value >>> 32));
If they are unordered you can use an arbitrary order which simplifies the rest of the code.
public BuildingPair(int pBuildingA,int pBuildingB) {
mBA = Math.min(pBuildingA, pBuildingB);
mBB = Math.max(pBuildingA, pBuildingB);
}
code the rest of the methods as normal and BuildingPair(2,1) will be exactly the same as BuildingPair(1,2)

Removing duplicates in TreeSet

I've been using ArrayList for my project to store a cricket team players and order them.
I started thinking about using a TreeSet because of its advantage of removing duplicates.
However the problem I'm having is that if for example I create the following two players:
P p1 = new P("Jack","Daniel",33(age),180(height),78(weight),41(games played),2300
(runs scored),41(dismisses))
P p2 = new P("Jack","Daniel",37(age),185(height),79(weight),45(games played),2560
(runs scored),45(dismisses))
Notice that the two players have the same first and last name, but everything else is different. When I try to add these two players to the TreeSet, it considers them duplicates because of the names similarities and removes the second one. Obviously I don't want this to happen and I want the Set to remove a player only if everything he has is the same as another player, and not just the first and last names.
Is there a way of achieving this?
Also my TreeSet takes a Player object.
Originally, this answer neglected the fact that a TreeSet does its comparisons based on compareTo(), rather than equals(). Edits have been made to address this.
You need to define equals(), hashCode() and compareTo() for your Player object correctly. (Since it's a TreeSet and not a HashSet, implementing hashCode() isn't so important - but it's good practice.)
Equals and hashCode need to take into account all of the fields. Eclipse can auto-generate one for you that will look similar to this (Source > Generate hashcode and equals).
If you already have a natural sort order that doesn't use all of the fields, then you could supply a custom comparator to your TreeSet. However, even if you really only want to sort by a subset of the fields, there's nothing stopping you sorting by all fields (with the uninteresting fields only playing a part of the interesting parts are identical). The important thing to note here is that a TreeSet determines equality not by the equals() method, but by compareTo() == 0.
Here's an example equals():
#Override
public boolean equals(Object obj)
{
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Player that = (Player) obj;
return this.age == that.age &&
this.height == that.height &&
this.weight == that.weight &&
this.games == that.games &&
this.runs == that.runs &&
this.dismisses == that.dismisses &&
this.given.equals(that.given) &&
this.family.equals(that.family);
}
And here's hashcode:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.age;
result = prime * result + this.dismisses;
result = prime * result + this.family.hashCode());
result = prime * result + this.games;
result = prime * result + this.given.hashCode());
result = prime * result + this.height;
result = prime * result + this.runs;
result = prime * result + this.weight;
return result;
}
Finally, here's a compareTo:
public int compareTo(Player that)
{
int result;
result = this.family.compareTo(that.family);
if (result != 0) // is the family name different?
{
return result; // yes ... use it to discriminate
}
result = this.given.compareTo(that.given);
if (result != 0) // is the given name different?
{
return result; // yes ... use it to discriminate
}
result = this.age - that.age; // is the age different?
if (result != 0)
{
return result; // yes ... use it to discriminate
}
... (and so on) ...
... with the final one ...
return this.dismisses - that.dismisses; // only thing left to discriminate by
}
a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.
From Java Platform Standard Edition 8 Documentation TreeSet part.
class Student implements Comparable<Student> {
String name;
public Student(String name) {
this.name=name;
}
public String toString(){
return name;
}
public int compareTo(Student gStudent) {
if(!this.name.equals(gStudent.getName()))
return 1;
return 0;
}
private String getName() {
return name;
}
}

Sorting custom data structure on Key in TreeMap

I am trying to sort a TreeMap on key. Key is some custom DataStructure having int, List, String, etc.
The member on which I am expecting a sort has some duplicates. Let's say that member is Rank. More than 1 object can have same rank.
Simplified version example:
NOTE: in the CompareTo method below 0 is not returned intentionally to NOT ignore duplicates.(Please correct me if this is not the right way to avoid duplicates)
import java.util.TreeMap;
public class TreeTest {
public static void main(String[] args) {
TreeMap<Custom,String> t = new TreeMap<Custom,String>();
Custom c1 = new Custom();
c1.setName("a");
c1.setRank(0);
Custom c2 = new Custom();
c2.setName("b");
c2.setRank(1);
Custom c3 = new Custom();
c3.setName("c");
c3.setRank(0);
t.put(c1, "first");
t.put(c2, "Second");
t.put(c3, "Third");
System.out.println(t.keySet());
for(Custom c:t.keySet()){
System.out.println(t.get(c));
}
}
}
And Custom Object
package com.example.ui;
public class Custom implements Comparable<Custom>{
int rank;
String name;
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + rank;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Custom other = (Custom) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (rank != other.rank)
return false;
return true;
}
// 0 is not returned intentionally to NOT ignore duplicates.
public int compareTo(Custom o) {
if(o.rank>this.rank)
return 1;
if(o.rank==this.rank)
return -1;
return -1;
}
}
Output::
[com.example.ui.Custom#fa0, com.example.ui.Custom#fbe, com.example.ui.Custom#f80]
null
null
null
Expected:
First, Second, Third based on Rank 0,1,0 respectively.
I looked at couple of examples on Google. Most of them were basic usage on TreeMap sort using keys or values with primitive datatypes, but none with duplicates when sorting member
is a part of custom key DataStructure.
Please help?
The problem is that your implementation of compareTo is not consistent with equals, which is required by TreeMap. From the API docs:
Note that the ordering maintained by a sorted map (whether or not an
explicit comparator is provided) must be consistent with equals if
this sorted map is to correctly implement the Map interface.
One possible consistent implementation would be to first compare by rank and then by name if the rank values are equal. For two instances of Custom with equal ranks and identical names you should not expect to be able to store them both as keys within the same Map - This violates the contract of Map.
public int compareTo(Custom o) {
int ret = this.rank - o.rank;
// Equal rank so fall back to comparing by name.
if (ret == 0) {
ret = this.name.compareTo(o.name);
}
return ret;
}
As mentioned, your implementation of equals and compareTo are not consistent with each other. If I read your question correctly, what you require is to preserve duplicates that have the same key. I'd recommend you to look into the TreeMultimap of the Google Guava collections. It creates set containers for each value object sothat different values having the same key are preserved.
e.g.
treeMultimap.put ("rank1", "Joe");
treeMultimap.put ("rank1", Jane");
treeMultimap.get ("rank1"); // Set("Joe","Jane");
The constrain in this data structure is that K,V pairs must be unique. That is, you can't insert ("rank1", "Joe") twice in the Multimap.
One important note: The reason why you see so many examples of Map, using simple types and, in particular, strings, is that keys in a map must be immutable. The equals and hashcode values of an object must not change in the time it's used as a key in a map. Translated to your example, you cannot do customObject.setRank(...) and updates a rank value when it's used as a key. To do so, you first need to remove the key and its values, update it and then insert it again.
You can also do it by implementing Comparator as anonymous inner type and override compare() to return desired comparison.
public class TreeMaps
{
public static void main(String[] args)
{
Custom c1 = new Custom(1,"A");
Custom c2 = new Custom(3,"C");
Custom c3 = new Custom(2,"B");
TreeMap<Custom , Integer > tree = new TreeMap<Custom, Integer> (new Comparator<Custom>() {
#Override
public int compare(Custom o1, Custom o2) {
return o1.rank - o2.rank;
}
});
tree.put(c1, 1);
tree.put(c2, 2);
tree.put(c3, 3);
System.out.println(tree);
}
}
class Custom
{
int rank ;
String name ;
public Custom(int rank , String name) {
this.rank = rank ;
this.name = name ;
}
#Override
public String toString()
{
return "Custom[" + this.rank + "-" + this.name + "]" ;
}
}

Categories