Optaplanner. School timetabling. Force first lession - optaplanner

I'm trying to add constraints to School timetabling example. For example: "all groups should have the first lesson".
I tried EasyScore and Streaming - no success. EasyScore cant finds a proper solution, shuffles lessons a lot. Streaming gave me an error: Undo for (Lesson(subj...)) does not exist
Code for Streaming:
from(Lesson::class.java)
.filter { it.timeslot != null }
.groupBy({ it.studentGroup }, { it.timeslot!!.day }, ConstraintCollectors.toList())
.filter { group, day, list ->
list.any { it.timeslot!!.number != 1 }
}
.penalize(
"Student must have first lesson",
HardSoftScore.ONE_HARD
) { group, day, list -> list.count { it.timeslot!!.number != 1 } },
Looks like I'm thinking the wrong direction.
https://github.com/Lewik/timetable
Any help will be greatly appreciated.
update: fixed == -> =!

As far as I understand it, I don't think you're enforcing what you intend to enforce. From what I make from your source code, you penalize every studentgroup's first lesson of the day.
What you should do to enforce the intended goal, is to penalize every studentgroup that does NOT have a timeslot with number == 1 but DOES have one (of the same day) where timeslot number != 1.
So something like :
join all Lesson.class instances with all Lesson.class instances where the first lesson's studentGroup equals the second lesson's studentGroup AND the first lesson's timeSlot's day equals the second lesson's timeSlot's day. You obtain a BiConstraintStream<Lesson, Lesson> this way...
from this, filter all Lesson.class instances where the first lesson's timeSlot's number is less than the second lesson's timeSlot number
then penalise the remaining where the first lesson's timeSlot number differs from 1. That equals penalising all of a studentGroup's days where they have some lesson that day without having any lesson that day during the first timeslot.
If I understood you correctly, that's what you wanted ?

I don't know the real source of the problem, but it's about hashCode. The exception was thrown because HashMap with Object key can't find by that Object.
Lesson class:
#Serializable
#NoArg
#PlanningEntity
data class Lesson(
val subject: String,
val teacher: String,
val studentGroup: String,
#PlanningVariable(valueRangeProviderRefs = ["timeslotRange"])
var timeslot: TimeSlot? = null,
#PlanningId
val id: String = UUID.randomUUID().toString(),
)
The implementation above will not work. It could be fixed if I remove data or add override fun hashCode() = Objects.hash(id). #PlanningId does not help here. Kotlin generates hashCode for data classes and seems it not working with optaplanner (or vise versa)

How about using .ifNotExists()?
First, convert student group from a String into a class and add #ProblemFactCollectionProperty List<StudentGroup> on your solution, then do
from(StudentGroup.class)
.ifNotExists(from(Lesson.class).filter(Lesson::isFirstTimeslot),
equals(this -> this, Lesson::getStudentGroup)
.penalize(...);

Related

Group by the day of the month - Kotlin Logic Problem

I'm stuck with the logic. So here it is, I have one model class Note:
data class Note(
val id: Int,
val title: String,
val description: String,
val date: Long = System.currentTimeMillis()
)
I have a list of multiple notes in my app List<Note>. And I need a way to convert that list into a Map. Where key will be the date: Long, and the value will be List<Note>. So: Map<Long, List<Note>> . I need to group those notes by the day of the month. For example, if multiple notes were created on October 31th, then they should be grouped in a single list of Notes, within a Map.
I'm really not sure how can I achieve that. Always had troubles with those date values. I will appreciate any help. :)
You can add a helper property to get the date in LocalDate format, which would make it easy to sort by day. If you were using this a lot, repeatedly, you might consider adding it as a member property that isn't computed on each retrieval (but not in the constructor because it is computed from another property that participates in equals and hashcode).
val Note.localDate: LocalDate
get() = Instant.ofEpochMilli(date).atZone(ZoneId.systemDefault()).toLocalDate()
Then you can use groupBy to create your Map of dates to lists.
val notesByLocalDate = notes.groupBy(Note::localDate) // or { it.localDate }
This is going to be one of the "just because you can, doesn't mean you should".
.groupBy {
val noteCalendar = Calendar.getInstance()
noteCalendar.timeInMillis = it.date
val day = noteCalendar.get(Calendar.DAY_OF_MONTH)
val month = noteCalendar.get(Calendar.MONTH)
val year = noteCalendar.get(Calendar.YEAR)
val dayCalendar = Calendar.getInstance()
dayCalendar.timeInMillis = 0L
dayCalendar.set(Calendar.DAY_OF_MONTH, day)
dayCalendar.set(Calendar.MONTH, month)
dayCalendar.set(Calendar.YEAR, year)
dayCalendar.set(Calendar.HOUR_OF_DAY, 12)
dayCalendar.timeInMillis
}
Trying to group something by its date of creation in milliseconds will result in no grouping because nothing is created at the exact same time*. So the only way for you to group it is to translate the date range of which those things happen into one value. In this case noon of that day to avoid timezone problems.
...but again I would recommend not grouping this by Long.

Joiners with filtering performs very slowly

I have a constraint with some joiners but the performance are very poor. Is it a way to improve it ?
I need to have the count of WorkingDay ( with ::hasPermission ) within the previous four days of the current day analyzed.
Here is my current constraint :
private Constraint fiveConsecutiveWorkingDaysMax(ConstraintFactory constraintFactory) {
return constraintFactory
.from(WorkingDay.class)
.filter(WorkingDay::hasPermission)
.join(WorkingDay.class,
Joiners.equal(WorkingDay::hasPermission),
Joiners.equal(WorkingDay::getAgent),
Joiners.filtering((wd1, wd2) -> {
LocalDate fourDaysBefore = wd1.getDayJava().minusDays(4);
Boolean wd2IsBeforeWd1 = wd2.getDayJava().isBefore(wd1.getDayJava());
Boolean wd2IsAfterFourDaysBeforeWd1 = wd2.getDayJava().compareTo(fourDaysBefore) >= 0;
return (wd2IsBeforeWd1 && wd2IsAfterFourDaysBeforeWd1);
}))
.groupBy((wd1, wd2) -> wd2, ConstraintCollectors.countBi())
.filter((wd2, count) -> count >= 4)
.penalizeConfigurable(FIVE_CONSECUTIVE_WORKING_DAYS_MAX);
}
Thanx for your help
There is potential for improvement here. First, we pre-filter the right hand side of the join to reduce the size of the cartesian product:
return constraintFactory
.forEach(WorkingDay.class)
.filter(WorkingDay::hasPermission)
.join(constraintFactory.forEach(WorkingDay.class)
.filter(WorkingDay::hasPermission),
Joiners.equal(WorkingDay::getAgent),
Joiners.filtering((wd1, wd2) -> {
LocalDate fourDaysBefore = wd1.getDayJava().minusDays(4);
Boolean wd2IsBeforeWd1 = wd2.getDayJava().isBefore(wd1.getDayJava());
Boolean wd2IsAfterFourDaysBeforeWd1 = wd2.getDayJava().compareTo(fourDaysBefore) >= 0;
return (wd2IsBeforeWd1 && wd2IsAfterFourDaysBeforeWd1);
}))
...
This has the added benefit of simplifying the index as it removes one equals joiner. Next, part of the filter can be replaced by a joiner as well:
return constraintFactory
.forEach(WorkingDay.class)
.filter(WorkingDay::hasPermission)
.join(constraintFactory.forEach(WorkingDay.class)
.filter(WorkingDay::hasPermission),
Joiners.equal(WorkingDay::getAgent),
Joiners.greaterThan(wd -> wd.getDayJava()),
Joiners.filtering((wd1, wd2) -> {
LocalDate fourDaysBefore = wd1.getDayJava().minusDays(4);
Boolean wd2IsAfterFourDaysBeforeWd1 = wd2.getDayJava().compareTo(fourDaysBefore) >= 0;
return wd2IsAfterFourDaysBeforeWd1;
}))
...
Finally, the method does needless boxing of boolean into Boolean, wasting CPU cycles and memory. This is a micro-optimization, but if the filter happens often enough, the benefit will be measurable.
A constraint refactored like this should perform better. That said, large joins are still going to take considerable time and the only way to work around that is to figure out a way to make them smaller.
Also, as Geoffrey said, I'd consider penalizing by the actual count, as what you have here is a textbook example of a score trap.
I don't see why this should be slow. Except maybe because the Cartesian Product explodes for a long time window. How many days is your time window?
Do note that the nurse rostering example has a totally different approach to detecting consecutive working days, using a custom collector. You might want to look at that in optaplanner-examples.

Java 8 Stream API - convert for loop over map & list iterator inside it

In the below code, I am trying to calculate the total price of a basket, where basket is a HashMap containing the products as key and the quantity as value. Promotions are available as a list of Promotion.
I am looping over every map entry and for each of them iterating the promotions. If the promotion matches, I am taking the promotion price (promotion.computeDiscountedPrice()) and removing the promotion from the list (Because a promotion is applicable only to a product & product is unique in the list)
If there is no promotion, we execute block.
if (!offerApplied) { /* .... */ }
Can you please help me in doing this same operation using JAVA 8 stream api?
BigDecimal basketPrice = new BigDecimal("0.0");
Map<String, Integer> basket = buildBasket(input);
List<Promotion> promotions = getOffersApplicable(basket);
for (Map.Entry<String, Integer> entry : trolley.entrySet()) {
boolean offerApplied = false;
Iterator<Promotion> promotionIterator = promotions.iterator();
while (promotionIterator.hasNext()) {
Promotion promotion = promotionIterator.next();
if (entry.getKey().equalsIgnoreCase(offer.getProduct().getProductName())) {
basketPrice = basketPrice.add(promotion.computeDiscountedPrice());
offerApplied = true;
promotionIterator.remove();
break;
}
if (!offerApplied) {
basketPrice = basketPrice.add(Product.valueOf(entry.getKey()).getPrice()
.multiply(new BigDecimal(entry.getValue())));
}
}
return basketPrice;
The simplest and cleaner solution, with a better performance than having to iterate the entire promotions list, is to start by creating a map of promotions identified by the product id (in lower case or upper case [assuming no case collision occurs by the use of equalsIgnoreCase(..)]).
Map<String, Promotion> promotionByProduct = promotions.stream()
.collect(Collectors.toMap(prom -> prom.getProduct()
.getProductName().toLowerCase(), Function.identity()));
This will avoid the need to iterate over the entire array when searching for promotions, it also avoids deleting items from it, which in case of being an ArrayList would need to shift to left the remaining elements each time the remove is used.
BigDecimal basketPrice = basket.keySet().stream()
.map(name -> Optional.ofNullable(promotionByProduct.get(name.toLowerCase()))
.map(Promotion::computeDiscountedPrice) // promotion exists
.orElseGet(() -> Product.valueOf(name).getPrice()) // no promotion
.multiply(BigDecimal.valueOf(basket.get(name))))
.reduce(BigDecimal.ZERO, BigDecimal::add);
It iterates for each product name in the basket, then checks if a promotion exists, it uses the computeDiscountedPrice method, otherwise it looks the product with Product.valueOf(..) and gets the price, after that it mutiplies this value by the quantity of products in the basket and finally the results are reduced (all values of the basket are added) with the BigDecimal.add() method.
Important thing to note, is that in your code, you don't multiply by the quantity the result of promotion.computeDiscountedPrice() (this code above does), i'm not sure if that is a type in your code, or that's the way it should behave.
If case it is in fact the way it should behave (you don't want to multiply quantity by promotion.computeDiscountedPrice()) the code would be:
BigDecimal basketPrice = basket.keySet().stream()
.map(name -> Optional.ofNullable(promotionByProduct.get(name.toLowerCase()))
.map(Promotion::computeDiscountedPrice)
.orElseGet(() -> Product.valueOf(name).getPrice()
.multiply(BigDecimal.valueOf(basket.get(name)))))
.reduce(BigDecimal.ZERO, BigDecimal::add);
Here the only value multiplied by quantity would be the product price obtained with Product.valueOf(name).getPrice().
Finally another option, all in one line and not using the map (iterating over the promotions) using the first approach (multipling by quantity in the end):
BigDecimal basketPrice = basket.keySet().stream()
.map(name -> promotions.stream()
.filter(prom -> name.equalsIgnoreCase(prom.getProduct().getProductName()))
.findFirst().map(Promotion::computeDiscountedPrice) // promotion exists
.orElseGet(() -> Product.valueOf(name).getPrice()) // no promotion
.multiply(BigDecimal.valueOf(basket.get(name))))
.reduce(BigDecimal.ZERO, BigDecimal::add);

DDD - Update Value Object in Database with FK dependencie

I am reading about DDD and I have learned that Value Object is immutable, if you want to change it, you will have to create a new one.
I have just read the information on How are Value Objects stored in the database? , it works well for Address class and I also read https://cargotracker.java.net/ and https://gojko.net/2009/09/30/ddd-and-relational-databases-the-value-object-dilemma/. But I want to do something different .
I am working on a billing system , it has 4 tables/classes
TPerson - fields: id_person, name -> <<Entity>>
TMobile - fields: id_mobile, number -> <<Entity>>
TPeriod - fields: id_period, id_person, id_mobile, begin_date, end_date -> <<Value Object>> (I think, because the dates can be change)
TCall - field: id_call, id_period, etc... -> <<Value Object>>
The table TCall has many records, if I change the period record dates (Value Object, table TPeriod) it will create another Object Period then id_period will change(delete, insert a record) , but the foreign key in table TCall will be violated. How Could I implement the period class ? if i implement as a value object , it will be immutable and turns out I will not be able to change anything whatsoever.
Thanks,
Fernando
if it's a value object you don't have a period table/id.
A value object is just a grouping of certain fields. For example a call might have a start time, an end time, and then you could create a Duration Value object with starttime and end time from the call table. In your java code it would be then more convenient to talk about the call duration instead of the start/end time separately.
However, it certainly could make sense to make period an entity, but then period 201601 probally always have the same start/end time and you wouldn't need to make changes to it. And if you did you make changes to the entity directly and keeping the ids in tact.
Thank for your help,
I have this situation:
TPerson - fields: id_person = 1 , name = "John"
TMobile - fields: id_mobile = 100, number "555-0123"
TPeriod - fields: id_period = 1000, id_person = 1 , id_mobile = 1, begin_date = "2016-01-01", end_date = "2049-12-31"
TCall - field: id_call = 1, id_period = 1000
The period is a relation between TPerson and TPeriod, in this example John has a mobile between "2016-01-01" and "2049-12-31". On the table TCall there are John's calls record, but if i replace the period (TPeriod table) end_date to "2016-02-01", from my understanding the end_date will be inconsistent, it turns out i cann't replace because it's a value object, not a entity. I considered to implement like this.
// Create a class DatePeriod
public class DatePeriod {
private final begin_date;
private final end_date;
DatePeriod() {}
public static DatePeriod of(Date begin_date, Date end_date) {
this.begin_date = begin_date;
this.end_date = end_date;
}
// implement equals / hashcode...
}
// Period class
public class Period {
int id;
// others mappings id_person / id_mobile
DatePeriod datePeriod;
}
Still, i will have to update datePeriod attribute
Thank you for your attention to this matter

Simple Linq-to-entities query involving .Include I believe

I have a Linq-to-Entities query that is not complicated but requires an .include and/or projection and/or join because it must be executed in one pass.
Here is my database (Microsoft SQL Server 2008):
Table A (Customers) (contains CustomerID (customer IDs), and ZipCode (zip codes) as strings.
Table C (Categories) (contains CategoryID (categories) like "food", "shelter","clothing", "housing" (primary keys).
Table A_C is a linking table, since Tables A and C are linked as many-to-many: contains just two fields: CustomerID "customer IDs" and CategoryID (Categories), in combination as primary keys. This table is a linking table betweeen tables A and C.
Here is my query, that must be executed in just one trip to the database: I need to select all records in Table A that satisfy a condition, then filter these records depending on a 'list of parameters' that are found in the linking Table A_C--and do this all in one trip to the database. But I don't know what the length or composition of the list of parameters for Table A_C is, ahead of time--it varies from call to call. Thus this list of parameters varies method call by method call.
To give a more concrete example:
Table A has a list of customer IDs. I find the customers that live in a certain Zip code. Then, in the same SQL query, I need to find which of these customers have selected certain categories: Food, Clothing, Housing, etc, but my web method does not know ahead of time what these categories are, rather, they are passed as a list to the method: List myCategoryList (which could be 1 category or 100 categories, and varies method call by method call).
How do I write the projection using Linq-to-Entities? When the list of parameters varies? And do it all in one pass?
List<string> CategoryList = new List<string>() { "Food", "Shelter", "Housing" }; // in one call to the web service method
List<string> CategoryList = new List<string>() { "Food", "Clothing" }; //could be a second call--it varies and I don't know ahead of time what the List will be
So how can I do the SQL query using Linq-to-Entities? In one pass? (Of course I could loop through the list, and make repeated trips to the database, but that's not an optimal solution I am told). Projection,.Include are keywords but surfing the net yielded nothing.
Here is a crude guess, just to get ball rolling:
public void WebMethod1 (CategoryList)
{
using (EntityFramework1 context = new EntityFramework1())
{
/* assume CategoryList is a list of strings passed into the method and is,for this particular call,something like: List<string> CategoryList = new List<string>() { "Food", "Clothing" }; for this call, but in the next call it could be: List<string> CategoryList = new List<string>() { "Food", "Shelter", "Housing" } */
string ZipCodeString = "12345";
string customerIDString = "E12RJ55";
var CustomersFromZipCodeHavingSelectedCertainCategories = from x in context.A_C
where x.A.CustomerID == customerIDString
where x.A.StartsWith(ZipCodeString)
where x.A_C.Contains(CategoryList) //???? This is clearly not grammatical, but what is?
select x;
}
/*
my problem is: I want to filter all records from A that contain a zipcode 12345, and that also have a certain CustomerID "E12RJ55" from table A, but further filter this set with all such CustomerIDs in linking table A_C that contain the categories "Food" and "Clothing".
How to do this in one pass? I can do this quite easily in multiple passes and trips to the database using code, but somebody in this thread here http://bit.ly/rEG2AM suggested I do a Join/projection and do it all in one fell swoop.
*/
I will also accept SQL answers since it might help yield a solution. This question btw is not difficult I believe--but I could not find an answer on the net.
EDIT: with answer and credit to david s.
I thank you for the answer david.s. Here is what worked, slightly different than the answer by david.s, in that I am using the linking table (bridge table) called “Customer_Categories” that is between the table Customer and Categories and contains the primary key of each (as is required for many-to-many relationships). This bridge table is what I called "A_C" in my original answer, and here has ints rather than strings but is the same thing. Intellisense picked up this table and I used it, and it works. Also keep in mind that CategoryList is a list of ints, List CategoryList = new List();, yet amazingly it automagically works inside this SQL-to-Entities query:
Var CustomersFromZipCOde = context.Customers.Where (custo => custo.CustomerID==customerIDString && custo.ZipCode.StartsWith(ZipCodeString) && custo.Customer_Categories.Any(categ => CategoryList.Contains(categ.CategoryID)));
//gives the right output, incredible.
First of all i would like to say that even if you explanation is very long it is not very clear. You would like a simple Linq-to-Entities query but you don't give the Entities, you only speak of tables in your database.
Assuming you have the following entities:
public class Customer
{
public string CustomerID { get; set; }
public string ZipCode { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public string CategoryID { get; set; }
public virtual ICollection<Customer> Customers { get; set; }
}
Your query might look like this:
var CustomersFromZipCodeHavingSelectedCertainCategories =
context.Customers.Where(
customer => customer.CustomerID == customerIDString &&
customer.ZipCode.StartsWith(ZipCodeString) &&
customer.Categories.Any(
category => CategoryList.Contains(category.CategoryID));
More info on other ways to do this here:
http://smehrozalam.wordpress.com/2010/06/29/entity-framework-queries-involving-many-to-many-relationship-tables/