Search code examples
javacomparatorhashsetcomparablesortedset

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 ?


Solution

  • 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.