Skip to content

Commit 8bb745d

Browse files
committed
chore: predictable ordering of collections when importing from XLS
1 parent 5bd27a8 commit 8bb745d

File tree

2 files changed

+73
-112
lines changed

2 files changed

+73
-112
lines changed

src/main/java/ai/timefold/solver/benchmarks/examples/conferencescheduling/persistence/ConferenceSchedulingXlsxFileIO.java

Lines changed: 44 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,17 @@
5252
import java.time.format.DateTimeParseException;
5353
import java.util.ArrayList;
5454
import java.util.Arrays;
55+
import java.util.Collection;
5556
import java.util.Comparator;
56-
import java.util.HashMap;
57-
import java.util.HashSet;
57+
import java.util.LinkedHashMap;
5858
import java.util.LinkedHashSet;
5959
import java.util.List;
6060
import java.util.Map;
6161
import java.util.Objects;
6262
import java.util.Set;
63+
import java.util.SortedMap;
64+
import java.util.TreeMap;
65+
import java.util.TreeSet;
6366
import java.util.function.Function;
6467

6568
import ai.timefold.solver.benchmarks.examples.common.persistence.AbstractXlsxSolutionFileIO;
@@ -113,11 +116,6 @@ public class ConferenceSchedulingXlsxFileIO extends
113116
private static final String TALK_PROHIBITED_ROOM_TAGS_DESCRIPTION =
114117
"Penalty per prohibited tag in a talk's room, per minute";
115118

116-
private static final String PUBLISHED_TIMESLOT_DESCRIPTION =
117-
"Penalty per published talk with a different timeslot than its published timeslot, per match";
118-
119-
private static final String PUBLISHED_ROOM_DESCRIPTION =
120-
"Penalty per published talk with a different room than its published room, per match";
121119
private static final String THEME_TRACK_CONFLICT_DESCRIPTION =
122120
"Penalty per common theme track of 2 talks with overlapping timeslots, per overlapping minute";
123121
private static final String THEME_TRACK_ROOM_STABILITY_DESCRIPTION =
@@ -157,10 +155,8 @@ public class ConferenceSchedulingXlsxFileIO extends
157155
"Penalty per missing preferred tag in a talk's room, per minute";
158156
private static final String TALK_UNDESIRED_ROOM_TAGS_DESCRIPTION = "Penalty per undesired tag in a talk's room, per minute";
159157

160-
private static final Comparator<Timeslot> COMPARATOR =
161-
comparing(Timeslot::getStartDateTime)
162-
.thenComparing(reverseOrder(comparing(
163-
Timeslot::getEndDateTime)));
158+
private static final Comparator<Timeslot> COMPARATOR = comparing(Timeslot::getStartDateTime)
159+
.thenComparing(reverseOrder(comparing(Timeslot::getEndDateTime)));
164160

165161
private final boolean strict;
166162

@@ -199,10 +195,10 @@ public ConferenceSchedulingXlsxReader(XSSFWorkbook workbook) {
199195
@Override
200196
public ConferenceSolution read() {
201197
solution = new ConferenceSolution();
202-
totalTalkTypeMap = new HashMap<>();
203-
totalTimeslotTagSet = new HashSet<>();
204-
totalRoomTagSet = new HashSet<>();
205-
totalTalkCodeMap = new HashMap<>();
198+
totalTalkTypeMap = new TreeMap<>();
199+
totalTimeslotTagSet = new TreeSet<>();
200+
totalRoomTagSet = new TreeSet<>();
201+
totalTalkCodeMap = new TreeMap<>();
206202
readConfiguration();
207203
readTimeslotList();
208204
readRoomList();
@@ -286,17 +282,15 @@ private void readTimeslotList() {
286282
readHeaderCell("Talk types");
287283
readHeaderCell("Tags");
288284
List<TalkType> talkTypeList = new ArrayList<>();
289-
List<Timeslot> timeslotList =
290-
new ArrayList<>(currentSheet.getLastRowNum() - 1);
285+
List<Timeslot> timeslotList = new ArrayList<>(currentSheet.getLastRowNum() - 1);
291286
var id = 0L;
292287
var talkTypeId = 0L;
293288
while (nextRow()) {
294-
var timeslot =
295-
new Timeslot(id++);
289+
var timeslot = new Timeslot(id++);
296290
var day = LocalDate.parse(nextStringCell().getStringCellValue(), DAY_FORMATTER);
297291
var startTime = LocalTime.parse(nextStringCell().getStringCellValue(), TIME_FORMATTER);
298292
var endTime = LocalTime.parse(nextStringCell().getStringCellValue(), TIME_FORMATTER);
299-
if (startTime.compareTo(endTime) >= 0) {
293+
if (!startTime.isBefore(endTime)) {
300294
throw new IllegalStateException(currentPosition() + ": The startTime (" + startTime
301295
+ ") must be less than the endTime (" + endTime + ").");
302296
}
@@ -359,12 +353,10 @@ private void readRoomList() {
359353
readHeaderCell("Talk types");
360354
readHeaderCell("Tags");
361355
readTimeslotHoursHeaders();
362-
List<Room> roomList =
363-
new ArrayList<>(currentSheet.getLastRowNum() - 1);
356+
List<Room> roomList = new ArrayList<>(currentSheet.getLastRowNum() - 1);
364357
var id = 0L;
365358
while (nextRow()) {
366-
var room =
367-
new Room(id++);
359+
var room = new Room(id++);
368360
room.setName(nextStringCell().getStringCellValue());
369361
if (strict && !VALID_NAME_PATTERN.matcher(room.getName()).matches()) {
370362
throw new IllegalStateException(currentPosition() + ": The room name (" + room.getName()
@@ -402,10 +394,8 @@ private void readRoomList() {
402394
}
403395
}
404396
totalRoomTagSet.addAll(room.getTagSet());
405-
Set<Timeslot> unavailableTimeslotSet =
406-
new LinkedHashSet<>();
407-
for (var timeslot : solution
408-
.getTimeslotList()) {
397+
Set<Timeslot> unavailableTimeslotSet = new LinkedHashSet<>();
398+
for (var timeslot : solution.getTimeslotList()) {
409399
var cell = nextStringCell();
410400
if (Objects.equals(extractColor(cell, UNAVAILABLE_COLOR), UNAVAILABLE_COLOR)) {
411401
unavailableTimeslotSet.add(timeslot);
@@ -446,12 +436,10 @@ private void readSpeakerList() {
446436
readHeaderCell("Undesired room tags");
447437

448438
readTimeslotHoursHeaders();
449-
List<Speaker> speakerList =
450-
new ArrayList<>(currentSheet.getLastRowNum() - 1);
439+
List<Speaker> speakerList = new ArrayList<>(currentSheet.getLastRowNum() - 1);
451440
var id = 0L;
452441
while (nextRow()) {
453-
var speaker =
454-
new Speaker(id++);
442+
var speaker = new Speaker(id++);
455443
speaker.setName(nextStringCell().getStringCellValue());
456444
if (strict && !VALID_NAME_PATTERN.matcher(speaker.getName()).matches()) {
457445
throw new IllegalStateException(currentPosition() + ": The speaker name (" + speaker.getName()
@@ -481,10 +469,8 @@ private void readSpeakerList() {
481469
speaker.setUndesiredRoomTagSet(Arrays.stream(nextStringCell().getStringCellValue().split(", "))
482470
.filter(tag -> !tag.isEmpty()).collect(toCollection(LinkedHashSet::new)));
483471
verifyRoomTags(speaker.getUndesiredRoomTagSet());
484-
Set<Timeslot> unavailableTimeslotSet =
485-
new LinkedHashSet<>();
486-
for (var timeslot : solution
487-
.getTimeslotList()) {
472+
Set<Timeslot> unavailableTimeslotSet = new LinkedHashSet<>();
473+
for (var timeslot : solution.getTimeslotList()) {
488474
var cell = nextStringCell();
489475
if (Objects.equals(extractColor(cell, UNAVAILABLE_COLOR), UNAVAILABLE_COLOR)) {
490476
unavailableTimeslotSet.add(timeslot);
@@ -501,10 +487,7 @@ private void readSpeakerList() {
501487
}
502488

503489
private void readTalkList() {
504-
var speakerMap =
505-
solution.getSpeakerList().stream().collect(
506-
toMap(Speaker::getName,
507-
speaker -> speaker));
490+
var speakerMap = toSortedMap(solution.getSpeakerList(), Speaker::getName, speaker -> speaker);
508491
nextSheet("Talks");
509492
nextRow(false);
510493
readHeaderCell("Code");
@@ -538,25 +521,17 @@ private void readTalkList() {
538521
readHeaderCell("Published Start");
539522
readHeaderCell("Published End");
540523
readHeaderCell("Published Room");
541-
List<Talk> talkList =
542-
new ArrayList<>(currentSheet.getLastRowNum() - 1);
524+
List<Talk> talkList = new ArrayList<>(currentSheet.getLastRowNum() - 1);
543525
var id = 0L;
544-
var timeslotMap =
545-
solution.getTimeslotList().stream().collect(
546-
toMap(timeslot -> {
547-
var key = timeslot.getStartDateTime();
548-
return new Pair<>(key, timeslot.getEndDateTime());
549-
},
550-
Function.identity()));
551-
var roomMap =
552-
solution.getRoomList().stream().collect(
553-
toMap(Room::getName,
554-
Function.identity()));
555-
Map<Talk, Set<String>> talkToPrerequisiteTalkSetMap =
556-
new HashMap<>();
526+
var timeslotMap = solution.getTimeslotList().stream().collect(
527+
toMap(timeslot -> {
528+
var key = timeslot.getStartDateTime();
529+
return new Pair<>(key, timeslot.getEndDateTime());
530+
}, Function.identity(), (a, b) -> a, LinkedHashMap::new));
531+
var roomMap = toSortedMap(solution.getRoomList(), Room::getName, Function.identity());
532+
Map<Talk, Set<String>> talkToPrerequisiteTalkSetMap = new LinkedHashMap<>();
557533
while (nextRow()) {
558-
var talk =
559-
new Talk(id++);
534+
var talk = new Talk(id++);
560535
talk.setCode(nextStringCell().getStringCellValue());
561536
totalTalkCodeMap.put(talk.getCode(), talk);
562537
if (strict && !VALID_CODE_PATTERN.matcher(talk.getCode()).matches()) {
@@ -662,9 +637,13 @@ private void readTalkList() {
662637
solution.setTalkList(talkList);
663638
}
664639

665-
private Timeslot extractTimeslot(
666-
Map<Pair<LocalDateTime, LocalDateTime>, Timeslot> timeslotMap,
667-
Talk talk) {
640+
private static <A, K extends Comparable<K>, K2 extends K, V> SortedMap<K2, V> toSortedMap(Collection<A> collection,
641+
Function<A, K2> keyFunction, Function<A, V> valueFunction) {
642+
return collection.stream()
643+
.collect(toMap(keyFunction, valueFunction, (a, b) -> a, TreeMap::new));
644+
}
645+
646+
private Timeslot extractTimeslot(Map<Pair<LocalDateTime, LocalDateTime>, Timeslot> timeslotMap, Talk talk) {
668647
Timeslot assignedTimeslot;
669648
var dateString = nextStringCell().getStringCellValue();
670649
var startTimeString = nextStringCell().getStringCellValue();
@@ -698,9 +677,7 @@ private Timeslot extractTimeslot(
698677
return null;
699678
}
700679

701-
private Room extractRoom(
702-
Map<String, Room> roomMap,
703-
Talk talk) {
680+
private Room extractRoom(Map<String, Room> roomMap, Talk talk) {
704681
var roomName = nextStringCell().getStringCellValue();
705682
if (!roomName.isEmpty()) {
706683
var room = roomMap.get(roomName);
@@ -753,13 +730,10 @@ private void verifyRoomTags(Set<String> roomTagSet) {
753730
}
754731
}
755732

756-
private void setPrerequisiteTalkSets(
757-
Map<Talk, Set<String>> talkToPrerequisiteTalkSetMap) {
758-
for (var entry : talkToPrerequisiteTalkSetMap
759-
.entrySet()) {
733+
private void setPrerequisiteTalkSets(Map<Talk, Set<String>> talkToPrerequisiteTalkSetMap) {
734+
for (var entry : talkToPrerequisiteTalkSetMap.entrySet()) {
760735
var currentTalk = entry.getKey();
761-
Set<Talk> prerequisiteTalkSet =
762-
new HashSet<>();
736+
Set<Talk> prerequisiteTalkSet = new LinkedHashSet<>();
763737
for (var prerequisiteTalkCode : entry.getValue()) {
764738
var prerequisiteTalk = totalTalkCodeMap.get(prerequisiteTalkCode);
765739
if (prerequisiteTalk == null) {
@@ -775,8 +749,7 @@ private void setPrerequisiteTalkSets(
775749

776750
private void readTimeslotDaysHeaders() {
777751
LocalDate previousTimeslotDay = null;
778-
for (var timeslot : solution
779-
.getTimeslotList()) {
752+
for (var timeslot : solution.getTimeslotList()) {
780753
var timeslotDay = timeslot.getDate();
781754
if (timeslotDay.equals(previousTimeslotDay)) {
782755
readHeaderCell("");

0 commit comments

Comments
 (0)