Java Comparator<T> giving unexpected result - java

I am implementing my own Java class for sorting a List<T> in eclipse. I made break points in comparsion statements and it does not work as I unexpected!
Here is the code :
if(doy2 < don && doy1>don)
{
return 1;
}
else if (doy2 > don && doy1<don)
{
return -1;
}
else
{
return 0;
}
Even though the doy2 > don && doy1<don statement evaluates to true, and the code reaches return -1, but it also goes to else part and return 0. Why exactly?
Edited: The Complete code
public class DateCompartor implements Comparator<BirthdayContact> {
#Override
public int compare(BirthdayContact arg0, BirthdayContact arg1) {
Date now=new Date();
Date bd1=arg0.GetBirthDay();
Date bd2=arg1.GetBirthDay();
DateTime dt1=new DateTime(bd1);
DateTime dtnow=new DateTime(now);
DateTime dt2=new DateTime(bd2);
int doy1=dt1.getDayOfYear();
int doy2=dt2.getDayOfYear();
int don=dtnow.getDayOfYear();
if(doy2 < don && doy1>don)
{
return 1;
}
else if (doy2 > don && doy1<don)
{
return -1;
}
else
{
return 0;
}
}
}
I am comparing two dates with current date, and which ever is closer to current date, should get upper in the list.

I'd rebuild your code and step through it again. The 'weirdness' that you note might be due to the source and bytecode being out of synch.
I'd ask that you post the entire Comparable implementation. Comparator usually compares two objects, but your comparison statements appear to involve three. Perhaps you can clarify exactly what you're doing.
Be assured that your code is incorrect. You have a mental model of how this should work in mind, but your assumptions don't match reality. Your job is to bring them back into harmony.
UPDATE:
Given the new information, I'd certainly not code it the way you did. I'd calculate the difference from each date to today's time and compare those. I'm not interested enough to test it out myself. See if that works out better for you.
public class DateCompartor implements Comparator<BirthdayContact> {
#Override
public int compare(BirthdayContact arg0, BirthdayContact arg1) {
Date now=new Date();
// Read Sun's Java coding standards; these don't follow the standard.
Date bd1=arg0.GetBirthDay();
Date bd2=arg1.GetBirthDay();
long dist1 = Math.abs(bd1.getTime() - now.getTime());
long dist2 = Math.abs(bd2.getTime() - now.getTime());
return dist1.compareTo(dist2); // Might need wrapper Long here.
}
}

Related

Comparison method violates its general contract in Java [duplicate]

I know this has been an issue for a while now, and checked all previously answers I could get, but still this one doesn't work.
The object 'crew' represents crewmembers with ranks and other items. The comparison should be made by comparing 'assigned_rank', an int value, and if this value is equal in both instances, then 'is_trainer', a boolean, should make the difference.
This method worked great as long as it was running with java < 7. But since Java 7 I keep getting this one:
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.ComparableTimSort.mergeLo(ComparableTimSort.java:714)
at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:451)
at java.util.ComparableTimSort.mergeCollapse(ComparableTimSort.java:376)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:182)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
at java.util.Arrays.sort(Arrays.java:472)
at java.util.Collections.sort(Collections.java:155)
at dormas_flightlog.Query.getCrew(Query.java:714)
Here is the source, where some potentially dangerous parts have allready been out-commented, but it still does not work:
public class crew implements Serializable, Comparable<crew> {
private static final long serialVersionUID = 36L;
private int flightID = 0;
private int assigned_rank = 25;
private boolean is_trainer = false;
...
#Override
public int compareTo(crew him) {
int myRank = this.getAssigned_rank();
int hisRank = him.assigned_rank;
if (this == him) {
return 0;
}
if (myRank > hisRank) {
return 1;
}
if (myRank < hisRank) {
return -1;
}
if (myRank == hisRank) {
// if (is_trainer && !o.is_trainer) {
// i = 1;
// }
// if (!is_trainer && o.is_trainer) {
// i = -1;
// }
// if (is_trainer && o.is_trainer) {
// i = 0;
// }
// if (!is_trainer && !o.is_trainer) {
// i = 0;
// }
return 0;
}
return 0;
}
#Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + this.assigned_rank;
hash = 31 * hash + (this.is_trainer ? 1 : 0);
return hash;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
int myRank = this.getAssigned_rank();
int hisRank = 0;
if (o instanceof crew) {
crew him = (crew) o;
hisRank = him.assigned_rank;
} else {
return false;
}
if (myRank > hisRank) {
return false;
}
if (myRank < hisRank) {
return false;
}
if (myRank == hisRank) {
// if (is_trainer && !o.is_trainer) {
// i = 1;
// }
// if (!is_trainer && o.is_trainer) {
// i = -1;
// }
// if (is_trainer && o.is_trainer) {
// i = 0;
// }
// if (!is_trainer && !o.is_trainer) {
// i = 0;
// }
return true;
}
return false;
}
}
Implementing equals() was just a try to solve this problem. The given exception comes with or without equals(). I cannot see how the compareTo-method violates its contract. Any help is greatly appreciated....one day this code has to work with java 7 and I don't know how...
Thanks
see this:
From http://www.oracle.com/technetwork/java/javase/compatibility-417013.html#source
Area: API: Utilities Synopsis: Updated sort behavior for Arrays and
Collections may throw an IllegalArgumentException
Description: The sorting algorithm used by java.util.Arrays.sort and
(indirectly) by java.util.Collections.sort has been replaced. The new
sort implementation may throw an IllegalArgumentException if it detects
a Comparable that violates the Comparable contract. The previous
implementation silently ignored such a situation. If the previous
behavior is desired, you can use the new system
property java.util.Arrays.useLegacyMergeSort, to restore previous
mergesort behavior.
Nature of Incompatibility: behavioral
RFE: 6804124
For more detailed info, see the bug database reference here.
maybe you just have NaN values which you compare through Collections.sort(...), this has been a problem to me and I got that exception even having right implementation of compare(obj1, obj2) method! Check that!
I was able to solve this error cause it was a bug in jdk7.
here I found the solution:
"Comparison method violates its general contract!" - TimSort and GridLayout
Basically i just had to add the
JAVA_OPTS="$JAVA_OPTS -Djava.util.Arrays.useLegacyMergeSort=true"
to my jboss
Unfortunately, none of the solutions work for Android. TimSort is used deep in Android's ViewGroup relating to addChildrenForAccessibility that shows up under Java 7 & 8. No user code is involved in any comparison.
From other reports, it is related to having RelativeLayout with overlapping items as is commonly done. For example, a TextView that appears over an Image, or two items at the same location, where you only set one visible at a time.
https://code.google.com/p/android/issues/detail?id=55933
I've not found any way around the bug. You can't set a -Djava option in Android Studio or Eclipse (at least that I could find). Forcing use of Java 1.6 should work, but doesn't. Seems like Amazon's newer Fire tablets and phones are far more sensitive to this bug than other devices.
There are rumors Java 9 will have a fix such as a run-time option that works, but with a bug that's been around for years, I have doubts it will ever be fixed - especially considering the animosity between Oracle and Google. Any yes, perhaps the bug is really deep in the Android code and should be fixed there. With more than a billion devices out there, that's not a viable solution for all the existing devices.

Why does my compare method violate its general contract?

public static Comparator<Container> DEPARTURE = new Comparator<Container>() {
#Override
public int compare(Container container1, Container container2) {
if (container1.departure.time.isBefore(container2.departure.time))
return -1;
else if (container1.departure.time.equals(container2.departure.time) &&
container1.departure.maxDuration == container2.departure.maxDuration &&
container1.departure.transportCompany.equals(container2.departure.transportCompany) &&
container1.departure.transportType == container2.departure.transportType)
return 0;
else
return +1;
}
};
the departure variable is just an instance of an object containing the following fields:
public DateTime time;
public int maxDuration;
public TransportType transportType;
public String transportCompany;
P.S. the time object is an instance of DateTime from the Joda-Time library and TransportType is an enumeration containing the constants Train, Seaship, Barge and Truck.
EDIT:
Ok, so, I edited my comparator to the following:
public static Comparator<Container> DEPARTURE = new Comparator<Container>() {
#Override
public int compare(Container container1, Container container2) {
if (container1.departure.time.isBefore(container2.departure.time))
return -1;
else if (container1.departure.time.isBefore(container2.departure.time))
return +1;
else {
if (container1.departure.maxDuration == container2.departure.maxDuration && container1.departure.transportType == container2.departure.transportType && container1.departure.transportCompany.equals(container2.departure.transportCompany))
return 0;
else
return +1;
}
}
};
but this obviously violates the general contract. How do I make it so it sorts by time and then sort those objects that have equivalent times by their other attributes only caring if they're equal or not? Hope this makes sense ...
EDIT: SOLUTION
Thank you all for answering my question! After studying your comments I came up with the following solution that seems to work (not thoroughly tested though):
I actually moved the comparing part to departure his class because I also need to compare by arrival. I decided to simply sort by all attributes (consecutively time, maxDuration, transportCompany and transportType) and the solution I came up with is:
public static Comparator<Container> ARRIVAL = new Comparator<Container>() {
#Override
public int compare(Container container1, Container container2) {
return container1.arrival.compareTo(container2.arrival);
}
};
public static Comparator<Container> DEPARTURE = new Comparator<Container>() {
#Override
public int compare(Container container1, Container container2) {
return container1.departure.compareTo(container2.departure);
}
};
And then the compareTo method:
#Override
public int compareTo(LocationMovement lm) {
if (this.time.isBefore(lm.time))
return -1;
else if (this.time.isAfter(lm.time))
return +1;
else {
int c = this.maxDuration - lm.maxDuration;
if (c != 0) return c;
c = this.transportCompany.compareTo(lm.transportCompany);
if (c != 0) return c;
c = this.transportType.ordinal() - lm.transportType.ordinal();
return c;
}
}
The general contract is that
COMPARATOR.compare(a, b) = - COMPARATOR.compare(b, a)
In your case, the code which returns -1 one way could return 0 the other.
In order to implement compare, all of the things you check must have the concept of being "lesser," "greater," or "equal" to one another, and then you must decide the order in which to check them, returning lesser/greater for the first of the items that isn't equal. That way, you satisfy the contract that compare(a, b) must be the converse of compare(b, a). If all of the parts of what you're comparing don't have the concept of "greater" or "lesser" (for instance, transport type), then either you can't implement compare or you must force an arbitrary (but reliable) greater/lesser interpretation on them.
Here's a conceptual example of doing that. In this case, the order I've chosen (arbitrarily) is: The time, the duration, the company, and the type. But a different order may be more reasonable. This is just an example. Also, you haven't said what the type of transportType is, so I've assumed it has a compareTo method; obviously it may not and you may have to adjust that.
public static Comparator<Container> DEPARTURE = new Comparator<Container>() {
#Override
public int compare(Container container1, Container container2) {
int rv;
// Times
rv = container1.departure.time.compareTo(container2.departure.time);
if (rv == 0) {
// Duration
if (container1.departure.maxDuration < container2.departure.maxDuration) {
rv = -1;
}
else if (container1.departure.maxDuration > container2.departure.maxDuration) {
rv = 1;
}
else {
// Transport company
rv = container1.departure.transportCompany.compareTo(container2.departure.transportCompany);
if (rv == 0) {
// Transport type
rv = container1.departure.transportType.compareTo(container2.departure.transportType);
}
}
}
return rv;
}
};
Note that if two containers c1 and c2 have equal departure.time, but differ in the other attributes, then both compare(c1, c2) and compare(c2, c1) will return +1, i.e., c1>c2 and c2>c1.
Instead, you should either drop the other fields entirely, or compare them separately in nested or sequential if-elses in case the departure time is equal.
Take a look at this answer to a related question for a clean way to compare objects by multiple attributes.

Scaling method fails to satisfy JUnit

I’m trying to make a position, length and circle classes based on given JUnit in order to eventually output them graphically. But I’m stuck in one of the methods for days now.
I tried to truncate precisions but then my equals method failed.
JUnit for Scale:
public void testScale(){
Length inch2 = Length.unit.scale(320.0);
assertTrue(inch2 != null);
assertEquals(Length.inch,inch2);
assertFalse(inch2.equals(Length.unit));
Length unit2 = Length.cm.scale(1.0/125.98425197);
assertTrue(unit2 != null);
assertEquals(Length.unit,unit2); // This is the line my scale method fails
// Here my unit2 has a length of 1.0001249999881234
// and my constant cm has a length of 1.0 but
// truncating those precisions caused my equals
// method to fails.
assertFalse(unit2.equals(Length.cm));
Length z = Length.meter.scale(0);
assertTrue(z != null);
assertEquals(Length.zero,z);
assertFalse(z.equals(Length.meter));
assertFalse(Length.zero.equals(null));
}
My scale method:
public Length scale(double d) {
if (d < 0)
throw new IllegalArgumentException();
else {
return new Length(d* this.length);
}
}
I suspect maybe the problem is coming from my equals method but in the given JUnit it is passing the tests.
JUnit for Equals:
public void testEquals(){
assertFalse(Length.unit.equals("Not a length"));
assertFalse(Length.inch.equals(null));
assertEquals(Length.zero,Length.unit.scale(0.0000001));
assertTrue(Length.unit.scale(0.0000001).compareTo(Length.zero) == 0);
assertTrue(Length.zero.compareTo(Length.unit.scale(0.0000001)) == 0);
assertFalse(Length.unit.scale(0.0000015).equals(Length.zero));
assertTrue(Length.unit.scale(0.0000015).compareTo(Length.zero) > 0);
assertTrue(Length.zero.compareTo(Length.unit.scale(0.0000015)) < 0);
}
My Equals Method:
#Override
public boolean equals(Object other) {
if (other == null || !(other instanceof Length)) {
return false;
}
Length o = (Length) other;
if (Math.abs(this.length - o.length) < 0.000001) {
return true;
} else {
return false;
}
}
Please help
Link for all my code:
https://www.dropbox.com/sh/bz400f8y0ufx381/59aUTilrBt
You are testing too many things at once.
A unit test should be one unit of code - one aspect of the code as opposed to everything at once.
I also notice that you don't have any of your test methods annotated with #Test; you should be doing this with JUnit4 tests.
So, for your first test, you have a relatively small scale method you want to exercise. Let's enumerate the cases:
d < 0. I should expect an IllegalArgumentException.
d >= 0. I should expect a new instance of Length with a size some multiple of d and whatever the set length of the instance is.
What this looks like is two discrete tests:
#Test(expected = IllegalArgumentException.class)
public void scaleShouldThrowExceptionWhenInvalidLength() {
}
#Test
public void scaleShouldBehaveNormally() {
}
I leave you to fill in the blanks, since I don't know what object scale is attached to.
Equals is the same way - you want to exercise each condition of the equivalence.
By the way, you can do return Math.abs(this.length - o.length) < 0.000001 for your conditions. return true and return false scream bad practice.
The object you're passing in is null.
The object you're passing in is not an instance of Length.
The object you're passing in fails Math.abs(this.length - o.length) < 0.000001.
The object you're passing in passes Math.abs(this.length - o.length) < 0.000001.
So the above are four discrete tests.
#Test
public void equalsShouldFailIfNull() {
}
#Test
public void equalsShouldFailIfNotInstanceOfLength() {
}
#Test
public void equalsDoesNotMeetCondition() {
}
#Test
public void equalsMeetsCondition() {
}
Filling in the blanks, I leave as an exercise to the reader.
Be very careful when dealing with floating-point numbers. You won't always get an exact representation back (that is, you may get an imprecise value when dealing with fractions). Be certain that your equals method is well-defined to respect what could happen when you don't have an exact decimal value to work with.
Alternatively, if you really need the decimal precision, use a BigDecimal instead.

Java - Better idiom for my control flow

I'd like to call a method that either returns false, or an integer. At the moment my code is:
int winningID = -1;
if((ID = isThereAWinner()) != -1) {
// use the winner's ID
} else {
// there's no winner, do something else
}
private int isThereAWinner() {
// if a winner is found
return winnersID;
// else
return -1;
}
I don't like the if((ID = isThereAWinner()) != -1) bit as it doesn't read very well, but unlike C you can't represent booleans as integers in Java. Is there a better way to do this?
I would use something similar to Mat's answer:
class Result {
public static Result withWinner(int winner) {
return new Result(winner);
}
public static Result withoutWinner() {
return new Result(NO_WINNER);
}
private static final int NO_WINNER = -1;
private int winnerId;
private Result(int id) {
winnerId = id;
}
private int getWinnerId() {
return winnerId;
}
private boolean hasWinner() {
return winnerId != NO_WINNER;
}
}
This class hides the implementation details of how you actually represent if there were no winner at all.
Then in your winner finding method:
private Result isThereAWinner() {
// if a winner is found
return Result.withWinner(winnersID);
// else
return Result.withoutWinner();
}
And in your calling method:
Result result = isThereAWinner();
if(result.hasWinner()) {
int id = result.getWinnerId();
} else {
// do something else
}
It may seem a little bit too complex, but this approach is more flexible if there would be other result options in the future.
What about something like:
private int getWinnerId() {
// return winner id or -1
}
private boolean isValidId(int id) {
return id != -1; // or whatever
}
int winnerId = getWinnerId();
if (isValidId(winnerId)) {
...
} else {
...
}
This is all quite subjective of course, but you usually expect an isFoo method to provide only a yes/no "answer".
The problem is you are trying to return two values at once. The approach you have taken is the simplest for this. If you want a more OO or design pattern approach I would use a listener pattern.
interface WinnerListener {
void onWinner(Int winnerId);
void noWinner();
}
checkWinner(new WinnerListener() {
// handle either action
});
private void checkWinner(WinnerListener wl) {
// if a winner is found
wl.onWinner(winnersID);
// else
wl.noWinner();
}
This approach works well with complex events like multiple arguments and multiple varied events. e.g. You could have multiple winners, or other types of events.
I'm afraid not. To avoid errors caused by mistaking if(a == b) for if(a = b), Java removes the conversion between boolean type and number types. Maybe you can try exceptions instead, but I think exception is somewhat more troublesome. (My English is not quite good. I wonder if I've made it clear...)
Perhaps you may wish to consider exceptions to help you with your understanding of asthetics of coding.
Use Integer instead of int and return null instead of -1. Look from this point: "I am returning not integer, but some object that represents winner identity. No winner - no instance"
Joe another suggestion, this is constructed based on #Mat and #buc mentioned little while ago, again this is all subjective of course I'm not sure what the rest of your class/logic is. You could introduce an enum with different ResultStatuses if it makes sense within the context of your code/exmaple.
As Matt mentioned you would expect isValid method to return a boolean yes/no (some may also complain of readability)
public enum ResultStatus {
WINNER, OTHER, UNLUCKY
}
This could be an overkill as well and depends on the rest of your logic (and if logic is expanding) but I thought I'll suggest nonetheless my two cents! So therefore in your public class (similar to #bloc suggested) you could have a method such as below that will return the status of the result checked.
public ResultStatus getResultStatus() {
if (isWinner()) {
return ResultStatus.WINNER;
} else {
return isOtherCheck() ? ResultStatus.OTHER : ResultStatus.UNLUCKY;
}
}

Sorting method not sorting correctly

I'm calling Arrays.sort(schedule, c); where c is an instance of a comparator like so:
import java.util.Comparator;
public class FirstOccComparator implements Comparator<AbstractEvent> {
public int compare(AbstractEvent event1, AbstractEvent event2) {
int result = 0;
if (event1 == null || event2 == null) {
//System.out.println("null");
}
else if (event1.hasMoreOccurrences() && event2.hasMoreOccurrences()) {
result = event1.nextOccurrence().compareTo(event2.nextOccurrence());
}
return result;
}
}
The output I'm getting isn't what it's supposed to be. I'm wondering if someone can point me in the right direction here. This is the first sorting algorithm I've ever made and its using concepts that are still new to me (comparators and implementation), so sorry about the multiple questions regarding my code :)
EDIT
This is the difference between the outputs: http://pastebin.com/LWy1jqkt
There are two kinds of events, these are the hasMoreOccurrences() and nextOccurrence() methods:
DailyEvent
public boolean hasMoreOccurrences() {
boolean result = false;
Date check = nextOccurrence();
timesCalled--;
if (check instanceof Date && check != null) {
result = true;
}
return result;
}
public Date nextOccurrence() {
if (timesCalled > recurrences) {
return null;
}
else {
Calendar cal = Calendar.getInstance();
cal.setTime(startTime);
cal.add(Calendar.DATE, timesCalled);
timesCalled++;
return cal.getTime();
}
}
WeeklyEvent
public boolean hasMoreOccurrences() {
Date tmp = nextOccurrence();
timesCalled--;
boolean result = false;
if (tmp instanceof Date && tmp != null) {
result = true;
}
return result;
}
public Date nextOccurrence() {
Calendar cal = Calendar.getInstance();
cal.setTime(startTime);
cal.add(Calendar.DATE, timesCalled*7);
if (cal.getTime().compareTo(this.endTime) > 0) {
return null;
}
else {
timesCalled++;
return cal.getTime();
}
}
There are a few things that seem incorrect with your comparator.
For instance, what happens if only one of them is null? How do you want those to sort? Right now you are considering two events equal if one of them is null.
Also, what happens if one event has more occurences while the other does not? Right now you only do comparisons on occurrences if both events have more occurrences. You need to handle the case where one has occurences while the other does not.
Also, if the occurence is a custom class, you need to evaluate that comparator as well.
When behavior doesn't match your assumptions, perhaps it's time to check your assumptions.
"...isn't what it's supposed to be..." suggests that you've got a notion of how your Comparator is supposed to work that isn't matching the output. Since the sorting algorithm built into Collections is proven, I think you need to look at your class and its Comparator for the error.
Write some unit tests and see where you've gone wrong.
Without seeing the results and the class you're sorting, it's impossible to advise you on what to do next.
Ask yourself: what happens when event1 and event2 are not null but one (or both) has run out of occurrences?
Your algorithm for determining equality is deeply flawed. Read this for more details: http://blogs.msdn.com/b/oldnewthing/archive/2003/10/23/55408.aspx?wa=wsignin1.0

Categories

Resources