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
Related
Sonar gives a major violation error ("Simplify Boolean Expression") for the following code.Following returns a Boolean value of matching date method .What are the steps should I take to overcome this violation. tnx
private boolean matchDate(Calendar createdDate, DateDomain dateRange) {
Calendar fromDateCal = Calendar.getInstance();
fromDateCal.setTime(dateRange.getDateFromD());
Calendar toDateCal = Calendar.getInstance();
toDateCal.setTime(dateRange.getDateToD());
if (createdDate.after(fromDateCal) && createdDate.before(toDateCal)) {
return true;
}
else {
return false;
}
}
Not a Sonar guru , but I would suggest you to do ,
private boolean matchDate(Calendar createdDate, DateDomain dateRange) {
Calendar fromDateCal = Calendar.getInstance();
fromDateCal.setTime(dateRange.getDateFromD());
Calendar toDateCal = Calendar.getInstance();
toDateCal.setTime(dateRange.getDateToD());
return createdDate.after(fromDateCal) && createdDate.before(toDateCal);
}
I hope that is what it telling.
Instead of checking the boolean and returning it again a boolean, it's better to use that boolean as a return param.
You can remove the if else and directly return the following way.
return createdDate.after(fromDateCal) && createdDate.before(toDateCal);
You if and else are redundant
Just use
return createdDate.after(fromDateCal) && createdDate.before(toDateCal);
No need redundant boolean values again and again using if-else.
This is a check style violation. of sonar
SimplifyBooleanExpression
Description
Checks for overly complicated boolean expressions. Currently finds code like if (b == true), b || true, !false, etc.
Rationale: Complex boolean logic makes code hard to understand and maintain.
**Instead of using true false directly, just return them like above solutions.**
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.
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.
}
}
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;
}
}
public void display(Date date) {
boolean loop = true;
System.out.println("Events on " + date.toString());
for (int i = 0; i < schedule.length; i++) {
while (loop) {
Date tmp = schedule[i].nextOccurrence();
if (tmp.compareTo(date) == 0) {
System.out.println(schedule[i].nextOccurrence().toString());
}
}
schedule[i].init();
}
}
The above is supposed to print out an occurrence of an event if it falls on the date given to the method. The method nextOccurrence grabs the next occurrence of an event (if its weekly or daily). nextOccurence looks like this for a DailyEvent:
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();
}
}
I call schedule[i].init() to reset the number of times called to 0 (daily events have a limit of number of times they can be called, denoted as an int with the variable recurrences).
Basically, my problem is that I'm getting a NullPointerException for this line:
if (tmp.compareTo(date) == 0) {
I've tried everything and I'm completely lost.
Any help would be great!
your method nextOccurence may return null, you need to check for that :
if (tmp != null && tmp.compareTo(date) == 0) {
Also, your loop variable is never set to false and will cause an infinite loop... And you call nextOccurrence() twice within your loop; is that desired?
You might consider redesigning your getNextOccurence() and have your schedule class implement an iterator for your dates. This will change your loop with
Iterator<Date> iterator = schedule[i].occurenceIterator();
Date tmp;
while (iterator.hasNext()) {
tmp = iterator.next();
if (tmp.compareTo(date) == 0) {
System.out.println(tmp.toString());
}
}
which is cleaner than what you're using.
I have a feeling that your nextOccurrence() function is returning null. If it returns null, then you'll get a NullPointerException when you try to use the compareTo function on that object.
Why didn't you step through this in a debugger?
My guess is that
public Date nextOccurrence() {
if (timesCalled == recurrences) {
return null; // <<<<
}
the line with '<<<<' is the root of the problem.