How to penalize gaps between days in OptaPlanner constraint stream? - java

I have a model where each Course has a list of available TimeSlots from which one TimeSlot gets selected by OptaPlanner. Each TimeSlot has a dayOfWeek property. The weeks are numbered from 1 starting with Monday.
Let's say the TimeSlots are allocated such that they occupy days 1, 3, and 5. This should be penalized by 2 since there's one free day between Monday and Wednesday, and one free day between Wednesday and Friday. By using groupBy(course -> course.getSelectedTimeslot().getDayOfWeek().getValue()), we can get a list of occupied days.
One idea is to use a collector like sum(), for example, and write something like sum((day1, day2) -> day2 - day1 - 1), but sum(), of course, works with only one argument. But generally, maybe this could be done by using a custom constraint collector, however, I do not know whether these collectors can perform such a specific action.
Another idea is that instead of summing up the differences directly, we could simply map each consecutive pair of days (assuming they're ordered) to the difference with the upcoming one. Penalization with the weight of value would then perform the summing for us. For example, 1, 4, 5 would map onto 2, 0, and we could then penalize for each item with the weight of its value.
If I had the weeks in an array, the code would look like this:
public static int penalize(int[] weeks) {
Arrays.sort(weeks);
int sumOfDifferences = 0;
for (int i = 1; i < weeks.length; i++) {
sumOfDifferences += weeks[i] - weeks[i - 1] - 1;
}
return sumOfDifferences;
}
How can we perform penalization of gaps between days using constraint collectors?

An approach using a constraint collector is certainly possible, see ExperimentalCollectors in optaplanner-examples module, and its use in the Nurse Rostering example.
However, for this case, I think that would be an overkill. Instead, think about "two days with a gap inbetween" as "two days at least 1 day apart, with no day inbetween". Once you reformulate your problem like that, ifNotExists(...) is your friend.
forEachUniquePair(Timeslot.class,
Joiner.greaterThan(slot -> slot.dayOfWeek + 1))
.ifNotExists(Timeslot.class,
Joiners.lessThan((slot1, slot2) -> slot1.dayOfWeek, TimeSlot::dayOfWeek),
Joiners.greaterThan((slot1, slot2) -> slot2.dayOfWeek, TimeSlot::dayOfWeek))
...
Obviously this is just pseudo-code, you will have to adapt it to your particular situation, but it should give you an idea for how to approach the problem.

Related

Optaplanner: Convert DRL to ConstraintStreams

I have an employee rostering application. Some of the rules are quite similar as in the Nurse Rostering example.
In the last two months I had to convert around 30 rules written in DRL to ConstraintSteams. After struggeling at the beginning I started to like it more and more. At the end I really liked it. Thanks a lot for this awesome work!
I want to mention and ask a few things:
ConsecutiveWorkingDays: Here I use the org.optaplanner.examples.common.experimental.ExperimentalConstraintCollectors to solve it. It works perfect. But comparing to the most other rules (I benchmarked the one by one as described in the documentation), it performes not that good (12,150/s), comparing to dayOffRequest (27,384/s) or fairDistributionGroup (21,864/s). But I imagine thats the complexity of the problem.
Consecutive Working Days 2: What is the best way to include org.optaplanner.examples.common.experimental.ExperimentalConstraintCollectors and these classes in the project? I copied them from the example to the Project.
SleepTime: The law in Switzerland has several rules for how much sleep time are legal. The description here, is minimal simplified:
Less then 8 hours sleep is allways illegal
Twice less the 11 hours sleep in one week is also allways illegal
Once less then eleven hours sleep is illegal, if the average sleep time in the week is less then 11 hours.
With the drl I did an InsertLogical of Freetime-Objects. A Freetime starts at the end of the last shift of a day and ends with the beginning of the next shift, where nexShift.dayIndex > firstShift.DayIndex. This calculation is quite intensive, because an employee can have more then one shift per day.
These Freetime-Objects I also used to calculate the rule TwoDaysOfInARowPerWeek.
With the ConstraintStreams the selection of the relevant shifts is done for every of the four rules. This decreased the calculation speed quite a lot. With the drl it was between 3000/s and 4000/s. With ConstraintStreams it decreased to 1500/s to 2000/s.
Now I managed to do all the sleep time rules in one rule, so the selection of the shifts has not to be done 4 times, but only 2 times. And now the speed is okay (2700/s to 3500/s). But still, there is no way to do something like InsertLogical? Or what alternatives are there?
Here the code, how I select this shifts:
private BiConstraintStream<Shift, Shift> getEmployeeFreetimeRelevantShifts(ConstraintFactory constraintFactory) {
// Shift (1): Employee works on the Shift
return constraintFactory.forEach(Shift.class)
.filter(shift -> shift.isNotEmptyShift() && shift.getHelperShiftForFreetime() != "last")
// No later Shift on the same day as Shift (1)
.ifNotExistsOther(
Shift.class,
Joiners.equal(Shift::getEmployee),
Joiners.equal(Shift::getDayIndex),
Joiners.filtering((shift_1, shift_2)-> shift_2.isNotHelperShiftForFreetime()),
Joiners.filtering((shift_1, later_shift_on_same_day)-> shift_1.getEndDatetimeInMinutesOfDay() < later_shift_on_same_day.getEndDatetimeInMinutesOfDay())
)
// Shift (2) a shift on a later day than shift 1
.join(
Shift.class,
Joiners.equal(Shift::getEmployee),
Joiners.filtering((shift_1, shift_2)-> shift_2.getHelperShiftForFreetime() != "first"),
Joiners.filtering((shift_1, shift_2)-> shift_1.getDayIndex() < shift_2.getDayIndex())
)
// There is no shift after Shift 2 on a earlier day between 1 and 2
.ifNotExists(
Shift.class,
Joiners.equal((shift_1, shift_2)-> shift_1.getEmployee().getId(), not_existing_shift -> not_existing_shift.getEmployee().getId()),
Joiners.filtering((shift_1, shift_2, shift_between_1_and_2 )-> {
return shift_between_1_and_2.isNotHelperShiftForFreetime();
}),
Joiners.filtering((shift_1, shift_2, shift_between_1_and_2 )-> {
return shift_between_1_and_2.getDayIndex() > shift_1.getDayIndex() && shift_between_1_and_2.getDayIndex() < shift_2.getDayIndex();
})
)
// and there is no earlier shift on the same day before Shift 2
.ifNotExists(
Shift.class,
Joiners.equal((shift_1, shift_2)-> shift_1.getEmployee().getId(), not_existing_shift -> not_existing_shift.getEmployee().getId()),
Joiners.equal((shift_1, shift_2)-> shift_2.getDayIndex(), not_existing_shift -> not_existing_shift.getDayIndex()),
Joiners.filtering((shift_1, shift_2, not_existing_shift_before_shift_2 )-> {
return not_existing_shift_before_shift_2.isNotHelperShiftForFreetime();
}),
Joiners.filtering((shift_1, shift_2, not_existing_shift_before_shift_2 )-> {
return shift_2.getStartDatetimeInMinutesOfDay() > not_existing_shift_before_shift_2.getStartDatetimeInMinutesOfDay();
})
);
}
Execution of Rules with 0 score: In my case a user can select which rules he wants to be executed, if it should be a hard or a soft constraint and he can change the penalty value. I solve this with a #ConstraintConfiguration. But as far as I can see, also the rules with a penalty value of 0 are executed (but not penaltized). So if I disable all rules except of one rule, the speed is not higher then when I select all rules. is that correct? And is there a possibility to do that in a different way?
Again, thanks a lot for this awesome project!
First of all, thank you for your kind words, we appreciate that. If I may make one suggestion for your next question - ask your questions separately, as this "aggregate question" will make the answer needlessly hard to read and search.
Wrt. the experimental constraint collector - indeed, the performance of it is not ideal. It does a lot of things to give you a nice and useful API, at the expense of runtime performance. Wrt. using it in your own project - until we decide to make it a public API, copying it is how the collector is intended to be used.
Wrt. insertLogical - you are right that there is no such thing in Constraint Streams, and likely never will be. It may be a natural concept to people coming from Drools, and pretty much no one else. :-) The use case you describe (counting hours of sleep) may possibly be accomplished with shadow variables; the line between what should be done in shadow variables and in constraints is somewhat blurry.
Finally, when it comes to disabled constraints - you are right. We have a JIRA filed to eventually address that shortcoming.

Java most efficient way using a long epoch timestamp to detect a change in day

Problem situation: I have an incredibly high number of records all marked with a timestamp. I'm looping through all of them to do this and that but I need to detect when the day has changed.
Right now for each loop I'm doing:
cal.setTimeInMillis(record.time);
int currentDay = cal.get(Calendar.DAY_OF_WEEK);
Is this as slow as I imagine it is when it's running hundreds of thousands of times?
I imagine I'm missing a really simple modulo answer or something.
Edit: Time zone does not matter, the information I'm collecting more resolves around a consumable report for someone. 24 hours per report is more accurate, so realistically I don't have to worry about whether or not that's 5am - 5am or 3pm - 3pm, just that I was able to gather 24H worth of info.
Thanks all
After Andy Turner’s time test I am not necessarily convinved that you need any optimized solution. In any case, timsmelik’s suggestion is pretty straightforward: convert the time when the day changes to a count of milliseconds since the epoch so you only need to compare long values. I don’t find that it hurts readability very badly. So here it is in code. I am using and warmly recommending java.time, the modern Java date and time API, if only for the conversion from hours to milliseconds and for printing the results. Even when such a conversion seems trivial, it’s always best to leave to the standard library to do it. It’s more self-explanatory and less error-prone, and it’s easier for the reader to convince oneself that it’s correct.
final long twentyfourHoursAsMillis = Duration.ofHours(24).toMillis();
// Times are already sorted descending (from newest to oldest)
long[] times = { 1_611_718_370_000L, 1_611_632_000_000L,
1_611_631_970_000L, 1_611_459_150_000L };
List<List<Long>> chunks = new ArrayList<>();
List<Long> currentChunk = new ArrayList<>();
// Process first time separately to get started
currentChunk.add(times[0]);
long timeOfNextChunk = times[0] - twentyfourHoursAsMillis;
// Process remaining times
for (int i = 1; i < times.length; i++) {
long currentTime = times[i];
if (currentTime <= timeOfNextChunk) {
chunks.add(currentChunk);
currentChunk = new ArrayList<>();
do {
timeOfNextChunk -= twentyfourHoursAsMillis;
} while (currentTime <= timeOfNextChunk);
}
currentChunk.add(currentTime);
}
// Save last chunk, why not?
chunks.add(currentChunk);
// Print result
for (List<Long> chunk : chunks) {
String chunkAsString = chunk.stream()
.map(Instant::ofEpochMilli)
.map(Instant::toString)
.collect(Collectors.joining(", "));
System.out.println(chunkAsString);
}
Output is:
2021-01-27T03:32:50Z, 2021-01-26T03:33:20Z
2021-01-26T03:32:50Z
2021-01-24T03:32:30Z
I am printing Instant objects. They always print in UTC. For your situation you may want to do otherwise if you need to print the times at all.
You should add a check of your assumption that the times come in sorted order.
I have taken your word for it and broken into chunks at 24 hours. 24 hours may not even mean 5am - 5am but could mean for instance from 5 AM EST on March 13 to 6 AM EDT on March 14 because summer time (DST) has begun in the meantime. If you prefer to split at the same clock hour, the code can be modified to do that.

Maximum occurrence of any event in time range

I have collection time stamps, e.g 10:18:07.490,11:50:18.251 where first is the start time and second is end time for an event. I need to find a range where maximum events are happening just 24 hours of time. These events are happening in precision of milliseconds.
What I am doing is to divide 24 hours on millisecond scale, and attach events at every millisecond, and then finding a range where maximum events are happening.
LocalTime start = LocalTime.parse("00:00");
LocalTime end = LocalTime.parse("23:59");
for (LocalTime x = start; x.isBefore(end); x = x.plus(Duration.ofMillis(1))) {
for (int i = 0; i < startTime.size(); i++) {
if (startTime.get(i).isAfter(x) && endTime.get(i).isBefore(x))
// add them to list;
}
}
Certainly this is not a good approach, it takes too much memory. How I can do it in a proper way? Any suggestion?
A solution finding the first period of maximum concurrent events:
If you're willing to use a third party library, this can be implemented "relatively easy" in a SQL style with jOOλ's window functions. The idea is the same as explained in amit's answer:
System.out.println(
Seq.of(tuple(LocalTime.parse("10:18:07.490"), LocalTime.parse("11:50:18.251")),
tuple(LocalTime.parse("09:37:03.100"), LocalTime.parse("16:57:13.938")),
tuple(LocalTime.parse("08:15:11.201"), LocalTime.parse("10:33:17.019")),
tuple(LocalTime.parse("10:37:03.100"), LocalTime.parse("11:00:15.123")),
tuple(LocalTime.parse("11:20:55.037"), LocalTime.parse("14:37:25.188")),
tuple(LocalTime.parse("12:15:00.000"), LocalTime.parse("14:13:11.456")))
.flatMap(t -> Seq.of(tuple(t.v1, 1), tuple(t.v2, -1)))
.sorted(Comparator.comparing(t -> t.v1))
.window(Long.MIN_VALUE, 0)
.map(w -> tuple(
w.value().v1,
w.lead().map(t -> t.v1).orElse(null),
w.sum(t -> t.v2).orElse(0)))
.maxBy(t -> t.v3)
);
The above prints:
Optional[(10:18:07.490, 10:33:17.019, 3)]
So, during the period between 10:18... and 10:33..., there had been 3 events, which is the most number of events that overlap at any time during the day.
Finding all periods of maximum concurrent events:
Note that there are several periods when there are 3 concurrent events in the sample data. maxBy() returns only the first such period. In order to return all such periods, use maxAllBy() instead (added to jOOλ 0.9.11):
.maxAllBy(t -> t.v3)
.toList()
Yielding then:
[(10:18:07.490, 10:33:17.019, 3),
(10:37:03.100, 11:00:15.123, 3),
(11:20:55.037, 11:50:18.251, 3),
(12:15 , 14:13:11.456, 3)]
Or, a graphical representation
3 /-----\ /-----\ /-----\ /-----\
2 /-----/ \-----/ \-----/ \-----/ \-----\
1 -----/ \-----\
0 \--
08:15 09:37 10:18 10:33 10:37 11:00 11:20 11:50 12:15 14:13 14:37 16:57
Explanations:
Here's the original solution again with comments:
// This is your input data
Seq.of(tuple(LocalTime.parse("10:18:07.490"), LocalTime.parse("11:50:18.251")),
tuple(LocalTime.parse("09:37:03.100"), LocalTime.parse("16:57:13.938")),
tuple(LocalTime.parse("08:15:11.201"), LocalTime.parse("10:33:17.019")),
tuple(LocalTime.parse("10:37:03.100"), LocalTime.parse("11:00:15.123")),
tuple(LocalTime.parse("11:20:55.037"), LocalTime.parse("14:37:25.188")),
tuple(LocalTime.parse("12:15:00.000"), LocalTime.parse("14:13:11.456")))
// Flatten "start" and "end" times into a single sequence, with start times being
// accompanied by a "+1" event, and end times by a "-1" event, which can then be summed
.flatMap(t -> Seq.of(tuple(t.v1, 1), tuple(t.v2, -1)))
// Sort the "start" and "end" times according to the time
.sorted(Comparator.comparing(t -> t.v1))
// Create a "window" between the first time and the current time in the sequence
.window(Long.MIN_VALUE, 0)
// Map each time value to a tuple containing
// (1) the time value itself
// (2) the subsequent time value (lead)
// (3) the "running total" of the +1 / -1 values
.map(w -> tuple(
w.value().v1,
w.lead().map(t -> t.v1).orElse(null),
w.sum(t -> t.v2).orElse(0)))
// Now, find the tuple that has the maximum "running total" value
.maxBy(t -> t.v3)
I have written up more about window functions and how to implement them in Java in this blog post.
(disclaimer: I work for the company behind jOOλ)
It can be done significantly better in terms of memory (well, assuming O(n) is considered good for you, and you don't regard 24*60*60*1000 as tolerable constant):
Create a list of items [time, type] (where time is the time, and type is
either start or end).
Sort the list by time.
Iterate the list, and when you see a "start", increment a counter, and when you see a "end", decrememnt it.
By storing a "so far seen maximum", you can easily identify the single point where maximal number of events occuring on it.
If you want to get the interval containing this point, you can simply find the time where "first maximum" occures, until when it ends (which is the next [time, type] pair, or if you allow start,end to be together and not counted, just linear scan from this point until the counter decreases and time moved, this can be done only once, and does not change total complexity of the algorithm).
This is really easy to modify this approach to get the interval from the point

Group and count duplicate values in arraylist java

I have used ArrayLists in my application, so I do not want to move to HashMaps as I have seen a few answers using this but did not work in my case.
I am creating my final high school project which is a study time table scheduler. I have managed to sort the ArrayList by date of the exam, but now I am trying to analyze the time table to rate it and compare the ratings with other generated timetables to give the user the best timetable (with the highest value).
I have gotten the timetable to output what they will be doing on each day ie: Afrikaans, IT, LO, Maths, Afrikaans, Afrikaans, etc.
But it seems that it duplicates on the last one for a long time even if the two last exams are within 2 days or very close together, the last one will be more dominant in the entire list. Like this:
Exam Dates:
LO = 2 Sep
Maths = 5 Sep
IT = 9 Sep
Afrikaans = 10 Sep
Results:
LO
IT
Afrikaans
Maths
IT
Afrikaans
Afrikaans
Afrikaans
Afrikaans
As you can see, the result is much more weighted towards the last given subject.
Now I am trying to compare and group the data so I can see how many days were allocated to each subject, so using the above example I need it to look like this:
LO = 1
Maths = 1
IT = 2
Afrikaans = 5
So far I have this:
ArrayList<Day> days = new ArrayList<Day>();
For(int i = 0; i < timeTable.size(); i++) {
// need to group the days here
if(days.contains(timeTable.get(i))) {
days.get(i).incrementNumberOfDays();
} else {
days.add(timeTable.get(i));
}
}
But that is not grouping the data and counting the duplicates.
Please help, my project is due in a week and I have spent two months on it. I am almost finished and am stuck here.
Thanks in advance!
You can use java.util.Collections.frequency:
int count = Collections.frequency(timeTable, value);
Edit: answer to your last question in comment:
Surely there is a quick simple way to say this value occurred 3 times
in the arraylist?
It's necessary to mention that in your timeTable, assuming it contains two same Day - Afrikaans - 1, when you see they are the same, the contains function doesn't think so. Although they have the same value for each property, but not the hashcode.
You need to override the function of equals() to make sure that when comparing two Day, if they have the same value, they are regarded as one object.
Sorry for my poor English, hope you can get it.

Java Using a for loop to print calendar (logic help)

I have this assignment due by the end of the month and I'm already halfway done. We had to use Zeller's congruence to calculate the day of the week that the 1st landed on. From that point (and accounting for leap years), you have to print off a calendar of the corresponding year using loops. The teacher said that this should be a really short program and we should not try to brute force the the logic of the program (basically, don' t use a bunch of if else and loops for each month). I already have the math calculations and leap year part down, but I can't wrap my head around how to make this for loop work for numerous reasons:
How would I go about factoring in whitespaces for each month? Like how many nested loops will I actually need?
Would I need to write different loops for Leap years, months with 31 days, and months with 30 days, etc or can all this be handled with 1 giant nested loop?
For example, I just started off writing a for loop just to see if I could print off a typical 31 day calendar without any special formatting (whitespaces, Month name, days etc). Here was my first test at using a for loop:
for(int i = 1; i < 31; i++)
{
for(int j = 0; j < 7; j++)
{
System.out.print(i + "\t");
i++;
if(j == 6){
i--;}
if(i > 31)
{
break;
}
}
System.out.print("\n");
}
What is the logic behind using it for multiple months and whitespaces. I welcome all help, but please DO NOT GIVE ME SOURCE CODE. I want the satisfaction of getting this done with code I've written, and like I said, this is also an assignment and I won't really learn from copying and pasting code. Just throw me a bone about the logic I need to use. I can work in all other conditionals for special cases. Thanks guys.
Ok, you need to manage something which varies between a fixed set of values, so you basically need a way to access these variable data from within a loop.
The best tool to fullfil this problem is probably an array.
If you store inside an array, for example the duration of the months you will be able to access them in a straightforward way (through an index) so that you won't need any conditional chain, you will just need the index of the current month, eg monthDurations[i] == 31.
If you want to structure everything better you could define your own Month class or Day class or whatever you need to keep track of printing issues (eg white spaces or names) so that everything will be easily encapsulated and maintainable.
I would write a function to display one month with parameters: dayOfWeek, numberOfDays, [name, firstDayOfWeek]
dayOfWeek - what day of week is 1st day of the month
numberOfDays - number of days in this month
optional parameters You may implement if You like
name - name of the month
firstDayOfWeek - some calendars use Sundays other use Mondays as first day, if You like to have this flexibility You can implement this as well.
And the logic is simple You can do with just one loop that would iterate numberOfDays+dayOfWeek-1 times.
Inside You just need to check for end of week to make new lines and display empty spaces dayOfWeek-1 times in first week.

Categories

Resources