Sorting custom data structure on Key in TreeMap - java

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 + "]" ;
}
}

Related

Is it possible to enter duplicate value in HashSet?

I am trying to add duplicate values in HashSet by modifying its hashCode() and equals() method()?
I tried below code
public class dupSet {
static Set set= new HashSet();
#Override
public int hashCode() {
return (int) (100*Math.random());
}
#Override
public boolean equals(Object obj) {
return false;
}
public static void main(String[] args) throws ParseException {
set.add("a");
set.add("b");
set.add("a");
System.out.println(set);
}
}
As per my understanding if for two duplicate of "a" HashSet will first get hashCode() to get proper bucket and then check value of equals() if equals returns true then it will not add but if it return false then it will add.
So for adding duplicate value to my Set I override equals() which always return false but still set is not allowing duplicate values?
You hashCode method returns always zero. Have a look at the range of Math.random().
Second, you do not override equals and hashCode of the elements you add. You actually add a String. To make things work, you must implement a class and add instances of that class to you HashSet. The implemented class needs to override the equals and hashSet method, not the main class.
Third, as stated in the comments, you shouldn't do what you are doing. What you realy want is a ArrayList. By implementing the equals and hashCode methods this way, a fundamental contract is broken.
I read source code and from that I am able to understand how its work
so need some help
First of all
Set is a collection of well defined and distinct objects
So there is no question of adding duplicates values. But if you are interested in understanding how java achieve/implement this constraint , then you can start digging in the source code.
A HashSet is backed by HashMap which mean that it delegates it operations like add, remove, etc. to HashMap .Now When you call set.add("a"); then
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
is called, which in turn calls HashMap#put
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
The put method first calcuates the hash code of the object using
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
Once the hashCode is calculated the it calls
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
inside this method , it put the value because this condition
if ((p = tab[i = (n - 1) & hash]) == null)
is true and it then increments the modCount(which stores the number of times the HashMap has been structurally modified), checks if we need to resize the map and then call afterNodeInsertion and returns null
Now when you call set.add("b"); then the same logic runs again but this time the condition inside final V putVal method
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
holds true and due to this , the code
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
detects the existing mapping and thus return the oldValue . Hence preventing adding duplicate value.
You want the objects in the Set to include duplicates I assume (if just for curiosity keep reading, otherwise just choose other collection. this might help)
Let me make some corrections:
public class DupSet<E extends Comparable<E>>{
private Set<E> mySet = new HashSet<>();
//Implement add, remove and size
}
public class MyNeverEqualClass implements Comparable<MyNeverEqualClass>{
private static int stupidHash = 0;
private int num;
public MyNeverEqualClass(int num){
this.num = num;
}
#Override
public int compareTo(MyNeverEqualClass other){
double rnd = Math.random()*3 + 1
return (rnd > 1.5)? 1:-1;
}
#Override
public boolean equals(MyNeverEqualClass other){
return false;
}
#Override
public int hashCode(){
return stupidHash++;
}
}
public static void main(String[] args){
MyNeverEqualClass a = new MyNeverEqualClass(1);
MyNeverEqualClass b = new MyNeverEqualClass(1);
DupSet<MyNeverEqualClass> set = new DupSet<>();
set.add(a);
set.add(b);
}

Ordering a list via descending order of int field and then lexicographically if int field is equal

I am a little confused about how to implement comparators/comparable. I am trying to sort a list (currently an ArrayList, but this is can change...) whereby when Collections.Sort is called, it's objects are sorted by a specific integer field in descending order, and if that integer field is equal then by lexicographic order of the name field.
This is my object:
class Movie implements Comparator<Movie>, Comparable<Movie> {
public String _title;
public int _time;
public Movie(String title, int time) {
this._title = title;
this._time = time;
}
public Movie() {
}
#Override
public int compareTo(Movie o) {
return (this._title.compareTo(o._title));
}
#Override
public int compare(Movie arg0, Movie arg1) {
return (arg0._time > arg1._time) ? -1 : (arg0._time == arg1._time) ? 0 : 1;
}
}
At the moment it only sorts via the number fields. How do I go about making it sort via lexicographic order if the number fields are equal?
I am a little confused by the compareTo method. This defines the natural order right? So what does
(this._title.compareTo(o._title))
actually do?
Thank you so much for your help!
EDIT:
I got my desired output by adding an if statement in the compare method and returning "arg0.compareTo(arg1)". However I am still unsure as to the compareTo method (even after reading about on the net) and a simple explanation would be great.
I am a little confused by the compareTo method. This defines the
natural order right? So what does
(this._title.compareTo(o._title))
actually do?
The natural order of Strings is their lexicographical order.
try:
if (arg0._time > arg1._time){
return -1
} else if (arg0._time < arg1._time){
return 1;
} else {
return arg0._title.compareTo(arg1._title);
}
You can use the following pattern to define compareTo with more and more specific conditions:
#Override
public int compareTo(Movie other) {
int result = Integer.compare( this._time, other._time );
if (result == 0) {
result = this._title.compareTo( other._title );
}
if (result == 0) {
// result = compare even more specific field
}
return result;
}

Java TreeSet: remove and contains() not working

I have added some simple objects to a TreeSet, but when I call the TreeSet's remove() and contains() methods, they don't work. However, when I iterate over the set the object is printed. Employee objects shall be added to the set while the objects uniqueness is based on the objects name property. The Id property is the value that should be sorted, but which is not unique.
public class Employee {
private String name;
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// Two objects are considered equal if their names are equal
#Override
public boolean equals(Object o) {
if (o == null)
return false;
if (this == o)
return true;
if (o.getClass() == this.getClass()) {
Employee p = ( Employee) o;
if (p.getName() != null && this.getName() != null)
return this.getName().equals(p.getName());
else
return false;
} else {
return false;
}
}
}
//*******************************************************
public class EmployeeComp implements Comparator<Employee> {
// Sort Ids, but allow duplicates, hence this function is never returning 0
#Override
public int compare(Employee p1, Employee p2) {
int re = 0;
boolean scoreLt = (p1.getId() > p2.getId());
boolean scoreGt = (p1.getId() < p2.getId());
if(scoreLt)
re = -1;
if(scoreGt)
re = 1;
else
re = -1;
return re;
}
}
//*******************************************************
// Collection shall store unique names with ordered values like:
// Alex, 923
// Toni, 728
// Eddi, 232
// Peter, 232
// Eddi, 156 *** not allowed
import java.util.TreeSet;
public class Main {
private static EmployeeComp comp = new EmployeeComp();
private static TreeSet<Employee> employees = new TreeSet<Employee>(comp);
public static void main(String[] args) {
Employee p1 = new Employee();
p1.setName("Eddi");
p1.setId(232);
Employee p2 = new Employee();
p2.setName("Toni");
p2.setId(728);
Employee p3 = new Employee();
p3.setName("Peter");
p3.setId(232);
Employee p4 = new Employee();
p4.setName("Alex");
p4.setId(923);
employees.add(p1);
employees.add(p2);
employees.add(p3);
employees.add(p4);
// Here, contains() and remove() should check the object address
// and not perform their actions based on compareTo
}
}
A TreeSet inserts/removes according to the results of Comparable, not .equals()/.hashCode()!
This means, BTW, that the objects of your Set do implement Comparable (if they didn't, each time you'd have tried and inserted a member, you'd have been greeted with a ClassCastException).
To be more accurate, TreeSet is an implementation of SortedSet.
If you want a .equals()/.hashCode()-compatible set, use, for instance, a HashSet.
For the illustration, here is what happens with BigDecimal (posted a few hours ago here):
final BigDecimal one = new BigDecimal("1");
final BigDecimal oneDotZero = new BigDecimal("1.0");
final Set<BigDecimal> hashSet = new HashSet<>();
// BigDecimal implements Comparable of itself, so we can use that
final Set<BigDecimal> treeSet = new TreeSet<>();
hashSet.add(one);
hashSet.add(oneDotZero);
// hashSet's size is 2: one.equals(oneDotZero) == false
treeSet.add(one);
treeSet.add(oneDotZero);
// treeSet's size is... 1! one.compareTo(oneDotZero) == 0
To quote the javadoc for Comparable, it means that BigDecimal's .compareTo() is "not consistent with .equals()".
** EDIT ** As to what the OP wants:
a Collection which will not accept duplicate names;
a sorted view of that Collection which will sort against the user's id.
As mentioned above, you cannot have one collection which does both. The solution:
for the first, a HashSet;
for the second, a copy of that set into an ArrayList, then using Collections.sort().
This means .equals() and .hashCode() must act only on the name, while a custom Comparator will act on the id. The Comparator has no other choice but to be custom since it is a comparator which is not consisten with .equals() in any event.
As to the proposed code, there are problems.
First: Employee overrides .equals() but not .hashCode(). As such, Employee breaks the .equals() contract (one part of which is that if two objects are equal, they must have the same hashcode). What is more, .hashCode() is critical for HashSet to work at all. Fix:
#Override
public int hashCode()
{
return name == null ? 0 : name.hashCode();
}
#Override
public boolean equals(final Object obj)
{
if (obj == null)
return false;
if (this == obj)
return false;
if (!(obj instanceof Employee))
return false;
final Employee other = (Employee) obj;
return name == null ? other.name == null
: name.equals(other.name);
}
Second: the comparator is equally as broken as Employee since it breaks the Comparator contract (for any o1 and o2, o1.compareTo(o2) == - o2.compareTo(o1)). Fix:
public final class EmployeeComp
implements Comparator<Employee>
{
#Override
public int compare(final Employee o1, final Employee o2)
{
final int id1 = o1.getId(), id2 = o2.getId();
if (id1 == id2)
return 0;
return id1 > id2 ? 1 : -1;
}
}
Then, how to obtain a sorted copy of the set:
// "set" contains the unique employees
final List<Employee> sorted = new ArrayList<Employee>(set);
Collections.sort(list, new EmployeeComp());
DONE.
Your problem is conceptual.
If you want a sorted collection of unique objects: TreeSet
If you want a sorted collection were different objects can have the same comparison value for sorting purposes: PriorityQueue
Incidentally, the methods in a PriorityList are more suited to the usual needs of the second case than the ones on TreeSet. I used to think of it as TreeSet shortcomings. For example, to take the first item out of the collection.
Hope that helps :-)

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

implement comparable interface for bidirectional flows

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.

Categories