简体   繁体   中英

Why is my compareTo method for this class not returning edges <A,B> and <B,A> as the same thing?

Here(down below) is my MovieEdge class; nothing special, it points to 2 Actor objects which are the vertices of this edge. Also this is an unweighted and undirected graph but we were required to add a weight field even though it will always be initialized to 1 so weight can be ignored in the compareTo method.

So in my graph class I call on my method edgeSet which returns a set of all the edges but it is a set so it should only add unique edges BUT take these 2 edges below

Actor a = new Actor("James");
Actor b = new Actor("Dan");
MovieEdge one = new MovieEdge(a, b, 1, "Movie1");
MovieEdge one = new MovieEdge(b, a, 1, "Movie1");

My compareTo method should output 0 when comparing the 2 MovieEdges above because this is an undirected graph but when I call the following in my main method

    testGraph.addEdge(A, C, 1, "Movie1");
    testGraph.addEdge(B, C, 1, "Movie1");
    testGraph.addEdge(C, D, 1, "Movie3");
    testGraph.addEdge(D, A, 1, "Movie1");
    testGraph.addEdge(A, D, 1, "Movie1");
    testGraph.addEdge(A, E, 1, "Movie1");
    System.out.println(testGraph.edgeSet());

in my main method this is the output

[A via Movie1 to C, A via Movie1 to D, A via Movie1 to E, B via Movie1 to C, C via Movie1 to A, E via Movie1 to A, C via Movie3 to D]

notice how the set displays both A via Movie1 to E and E via Movie1 to A that is not right

I have been changing my 'compareTo' for since 2am (it's 7:43 am now) but i do not understand, my first 2 conditions that return 0 should take care of this problem but for some reason it is not.

Also I should point out that oddly when I add only a single edge like testGraph.addEdge(A, E, 1, "Movie1"); and call System.out.println(testGraph.edgeSet()); it prints correctly and only [A via Movie1 to E] prints on the console.

public class MovieEdge implements Comparable {

    private Actor source;
    private Actor destination;
    private int weight;
    private String movieName;

    public MovieEdge(Actor source, Actor destination, int wieght, String movieName){
        this.source = source;
        this.destination = destination;
        this.weight = weight;
        this.movieName = movieName;
    }

    public Actor getSource() {
        return source;
    }

    public Actor getDestination() {
        return destination;
    }

    public int getWeight(){
        return weight;
    }

    public String getMovieName() {
        return movieName;
    }

    public String toString(){
        return source + " via " + movieName + " to " + destination ;
    }

    @Override
    public int compareTo(Object o) {
        MovieEdge a = (MovieEdge)o;
        if(this.movieName.equals(a.movieName)){
            if(this.source.getName().equals(a.source.getName()) && this.destination.getName().equals(a.destination.getName())){
                return 0;
            }
            else if(this.source.getName().equals(a.destination.getName()) && this.destination.getName().equals(a.source.getName())){
                return 0;
            }
            else if(this.destination.compareTo(a.destination) != 0 || this.source.compareTo(a.source) !=0 ){
                return 1;
            }
            return 0;
        }
        if(this.movieName.compareTo(a.movieName) < 0){
            return -1;
        }else if(this.movieName.compareTo(a.movieName) > 0){
            return 1;
        }


        return -1;
    }

}

my edgeSet Method

public Set<MovieEdge> edgeSet() {
    Set<MovieEdge> values = new TreeSet<MovieEdge>();

    for (TreeSet<MovieEdge> value : adjList.values()) {
        for(MovieEdge m : value){
            values.add(m);
        }
    }

    return values;
}

adjList.values() is just each TreeSet of type MovieEdge for each actor vertex

My graph passes all my instructors JUNIT test so I am getting an A on this assignment but this bug is a personal thing, I MUST KNOW WTF IS WRONG. It is driving me nuts. Thank you.

My updated and still not working (giving me same problem as above) code for the MovieEdge class

`@Override 
public int hashCode(){
    return source.getName().hashCode() + destination.getName().hashCode();
}

@Override
public boolean equals(Object obj){
    MovieEdge a = (MovieEdge)obj;
    if((source.getName().equals(a.source.getName())) && (destination.getName().equals(a.destination.getName()))){
        return true;
    }
    if((source.getName().equals(a.destination.getName())) && (destination.getName().equals(a.source.getName()))){
        return true;
    }
    return false;
}

@Override
public int compareTo(Object o) {
    MovieEdge a = (MovieEdge)o;

    if(movieName.equals(a.movieName)){
        if(equals(a))
            return 0;
        else if(source.getName().compareTo(a.source.getName()) < 0)
            return -1;
        else
            return 1;
    }else if(movieName.compareTo(a.movieName) < 0)
        return -1;

    return 1;
}    `

Basically you have two errors in your code:

First, you implement a Comparable , but do not override the equals() method. From the Comparable documentation

It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.

Second your Comparable has a weird behaviour if movieName are equal but not the source and destination , because it always returns 1 . This breaks the compareTo contract :

(x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0

This leads to your TreeSet not working properly and thus your edge been added twice.


Edit: As far as I can see, there is no trivial implementation of the compareTo method. So I'd suggest using a HashSet with a convenient hashCode and equals method. Sth. like this:

@Override
public int hashCode()
{
    final int prime = 31;
    int result = 1;
    result = prime * result + Objects.hashCode(movieName);
    result *= prime;
    // Only add, because order independent
    result += Objects.hashCode(destination);
    result += Objects.hashCode(source);
    return result;
}

@Override
public boolean equals(Object obj)
{
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    MovieEdge other = (MovieEdge)obj;
    if (Objects.equals(movieName, other.movieName))
    {
        if (Objects.equals(source, other.source)
                && Objects.equals(destination, other.destination))
        {
            return true;
        }
        if (Objects.equals(source, other.destination)
                && Objects.equals(destination, other.source))
        {
            return true;
        }
    }
    return false;
}

The answer provided by Lumnitz is correct, I just wanted to add below points:

Set eliminates duplicates by validating the objects equality using equals(Object obj) method, which you did not override for your MovieEdge class.

Basically, you need to know the below points:

(1) Override equals() and hashcode() (from java.lang.Object ) for checking objects equality (your case is this ie, you wanted to eliminate two MovieEdge objects by checking some conditions using moviename , etc..)

public boolean equals(Object obj) : Indicates whether some other object is "equal to" this one.

(2) Override compareTo(Object o) (from java.lang.Comparable ) for ordering (ascending/ descending) the objects

public interface Comparable : This interface imposes a total ordering on the objects of each class that implements it

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM