Skip to content

Commit ab2784a

Browse files
committed
feat: containing/containedIn/intersecting joiners
1 parent e97de4b commit ab2784a

3 files changed

Lines changed: 20 additions & 21 deletions

File tree

src/main/java/ai/timefold/solver/benchmarks/examples/conferencescheduling/domain/Talk.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,6 @@ public Integer getDurationInMinutes() {
226226
return timeslot == null ? null : timeslot.getDurationInMinutes();
227227
}
228228

229-
public boolean overlapsTime(Talk other) {
230-
return timeslot != null && other.getTimeslot() != null && timeslot.overlapsTime(other.getTimeslot());
231-
}
232-
233229
public int overlappingDurationInMinutes(Talk other) {
234230
if (timeslot == null) {
235231
return 0;

src/main/java/ai/timefold/solver/benchmarks/examples/conferencescheduling/score/ConferenceSchedulingConstraintProvider.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,12 @@
3333
import static ai.timefold.solver.core.api.score.stream.ConstraintCollectors.countBi;
3434
import static ai.timefold.solver.core.api.score.stream.ConstraintCollectors.max;
3535
import static ai.timefold.solver.core.api.score.stream.ConstraintCollectors.min;
36+
import static ai.timefold.solver.core.api.score.stream.Joiners.containedIn;
37+
import static ai.timefold.solver.core.api.score.stream.Joiners.containing;
3638
import static ai.timefold.solver.core.api.score.stream.Joiners.equal;
3739
import static ai.timefold.solver.core.api.score.stream.Joiners.filtering;
3840
import static ai.timefold.solver.core.api.score.stream.Joiners.greaterThan;
41+
import static ai.timefold.solver.core.api.score.stream.Joiners.intersecting;
3942
import static ai.timefold.solver.core.api.score.stream.Joiners.lessThan;
4043
import static ai.timefold.solver.core.api.score.stream.Joiners.overlapping;
4144

@@ -117,8 +120,8 @@ Constraint speakerUnavailableTimeslot(ConstraintFactory factory) {
117120
return factory.forEachIncludingUnassigned(Talk.class)
118121
.filter(talk -> talk.getTimeslot() != null)
119122
.join(Speaker.class,
120-
filtering((talk, speaker) -> talk.hasSpeaker(speaker)
121-
&& speaker.getUnavailableTimeslotSet().contains(talk.getTimeslot())))
123+
containing(Talk::getSpeakerList, speaker -> speaker),
124+
containedIn(Talk::getTimeslot, Speaker::getUnavailableTimeslotSet))
122125
.penalize(HardSoftScore.ofHard(100), (talk, speaker) -> talk.getDurationInMinutes())
123126
.asConstraint(SPEAKER_UNAVAILABLE_TIMESLOT);
124127
}
@@ -127,7 +130,8 @@ Constraint speakerConflict(ConstraintFactory factory) {
127130
return factory.forEachUniquePair(Talk.class,
128131
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()))
129132
.join(Speaker.class,
130-
filtering((talk1, talk2, speaker) -> talk1.hasSpeaker(speaker) && talk2.hasSpeaker(speaker)))
133+
containing((talk1, talk2) -> talk1.getSpeakerList(), speaker -> speaker),
134+
containing((talk1, talk2) -> talk2.getSpeakerList(), speaker -> speaker))
131135
.penalize(HardSoftScore.ofHard(10), (talk1, talk2, speaker) -> talk2.overlappingDurationInMinutes(talk1))
132136
.asConstraint(SPEAKER_CONFLICT);
133137
}
@@ -136,18 +140,18 @@ Constraint talkPrerequisiteTalks(ConstraintFactory factory) {
136140
return factory.forEach(Talk.class)
137141
.join(Talk.class,
138142
greaterThan(t -> t.getTimeslot().getEndDateTime(), t -> t.getTimeslot().getStartDateTime()),
139-
filtering((talk1, talk2) -> talk2.getPrerequisiteTalkSet().contains(talk1)))
143+
containedIn(talk -> talk, Talk::getPrerequisiteTalkSet))
140144
.penalize(HardSoftScore.ofHard(10), Talk::combinedDurationInMinutes)
141145
.asConstraint(TALK_PREREQUISITE_TALKS);
142146
}
143147

144148
Constraint talkMutuallyExclusiveTalksTags(ConstraintFactory factory) {
145149
return factory.forEachUniquePair(Talk.class,
146-
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()))
147-
.expand((talk1, talk2) -> talk2.overlappingMutuallyExclusiveTalksTagCount(talk1))
148-
.filter((talk1, talk2, overlappingTagCount) -> overlappingTagCount > 0)
150+
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()),
151+
intersecting(Talk::getMutuallyExclusiveTalksTagSet))
149152
.penalize(HardSoftScore.ofHard(1),
150-
(talk1, talk2, overlappingTagCount) -> overlappingTagCount * talk1.overlappingDurationInMinutes(talk2))
153+
(talk1, talk2) -> talk2.overlappingMutuallyExclusiveTalksTagCount(talk1)
154+
* talk1.overlappingDurationInMinutes(talk2))
151155
.asConstraint(TALK_MUTUALLY_EXCLUSIVE_TALKS_TAGS);
152156
}
153157

@@ -231,21 +235,19 @@ Constraint themeTrackConflict(ConstraintFactory factory) {
231235
Constraint themeTrackRoomStability(ConstraintFactory factory) {
232236
return factory.forEachUniquePair(Talk.class,
233237
equal(talk -> talk.getTimeslot().getStartDateTime().toLocalDate()),
238+
intersecting(Talk::getThemeTrackTagSet),
234239
filtering((talk1, talk2) -> !talk1.getRoom().equals(talk2.getRoom())))
235-
.expand((talk1, talk2) -> talk2.overlappingThemeTrackCount(talk1))
236240
.penalize(HardSoftScore.ofSoft(10),
237-
(talk1, talk2, overlappingTrackCount) -> overlappingTrackCount * talk1.combinedDurationInMinutes(talk2))
241+
(talk1, talk2) -> talk2.overlappingThemeTrackCount(talk1) * talk1.combinedDurationInMinutes(talk2))
238242
.asConstraint(THEME_TRACK_ROOM_STABILITY);
239243
}
240244

241245
Constraint sectorConflict(ConstraintFactory factory) {
242246
return factory.forEachUniquePair(Talk.class,
243-
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()))
244-
.expand((talk1, talk2) -> talk2.overlappingSectorCount(talk1))
245-
.filter((talk1, talk2, overlappingSectorCount) -> overlappingSectorCount > 0)
247+
overlapping(t -> t.getTimeslot().getStartDateTime(), t -> t.getTimeslot().getEndDateTime()),
248+
intersecting(Talk::getSectorTagSet))
246249
.penalize(HardSoftScore.ofSoft(10),
247-
(talk1, talk2, overlappingSectorCount) -> overlappingSectorCount
248-
* talk1.overlappingDurationInMinutes(talk2))
250+
(talk1, talk2) -> talk2.overlappingSectorCount(talk1) * talk1.overlappingDurationInMinutes(talk2))
249251
.asConstraint(SECTOR_CONFLICT);
250252
}
251253

@@ -395,7 +397,7 @@ Constraint talkUndesiredRoomTags(ConstraintFactory factory) {
395397
Constraint speakerMakespan(ConstraintFactory factory) {
396398
return factory.forEach(Speaker.class)
397399
.join(Talk.class,
398-
filtering((speaker, talk) -> talk.hasSpeaker(speaker)))
400+
containedIn(speaker -> speaker, Talk::getSpeakerList))
399401
.groupBy((speaker, talk) -> speaker,
400402
compose(
401403
min((Speaker speaker, Talk talk) -> talk, talk -> talk.getTimeslot().getStartDateTime()),

src/main/java/ai/timefold/solver/benchmarks/examples/curriculumcourse/score/CurriculumCourseConstraintProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static ai.timefold.solver.core.api.score.HardSoftScore.ofSoft;
66
import static ai.timefold.solver.core.api.score.stream.ConstraintCollectors.count;
77
import static ai.timefold.solver.core.api.score.stream.ConstraintCollectors.countDistinct;
8+
import static ai.timefold.solver.core.api.score.stream.Joiners.containedIn;
89
import static ai.timefold.solver.core.api.score.stream.Joiners.equal;
910
import static ai.timefold.solver.core.api.score.stream.Joiners.filtering;
1011

@@ -154,7 +155,7 @@ Constraint curriculumCompactness(ConstraintFactory factory) {
154155
private static BiConstraintStream<Curriculum, Lecture> curriculumLectureLeft(PrecomputeFactory factory) {
155156
return factory.forEachUnfiltered(Curriculum.class)
156157
.join(Lecture.class,
157-
filtering((curriculum, lecture) -> lecture.getCurriculumSet().contains(curriculum)));
158+
containedIn(curriculum -> curriculum, Lecture::getCurriculumSet));
158159
}
159160

160161
Constraint roomStability(ConstraintFactory factory) {

0 commit comments

Comments
 (0)