
org.cpsolver.studentsct.online.selection.SuggestionsBranchAndBound Maven / Gradle / Ivy
package org.cpsolver.studentsct.online.selection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.cpsolver.coursett.Constants;
import org.cpsolver.coursett.model.TimeLocation;
import org.cpsolver.ifs.assignment.Assignment;
import org.cpsolver.ifs.util.DataProperties;
import org.cpsolver.ifs.util.ToolBox;
import org.cpsolver.studentsct.model.Config;
import org.cpsolver.studentsct.model.Course;
import org.cpsolver.studentsct.model.CourseRequest;
import org.cpsolver.studentsct.model.Enrollment;
import org.cpsolver.studentsct.model.FreeTimeRequest;
import org.cpsolver.studentsct.model.Request;
import org.cpsolver.studentsct.model.Section;
import org.cpsolver.studentsct.model.Student;
import org.cpsolver.studentsct.online.OnlineSectioningModel;
import org.cpsolver.studentsct.online.expectations.OverExpectedCriterion;
import org.cpsolver.studentsct.online.selection.MultiCriteriaBranchAndBoundSelection.SelectionComparator;
/**
* Computation of suggestions using a limited depth branch and bound.
* For a given schedule and a selected section, compute
* possible changes that will be displayed to the student as suggestions. The number
* of suggestions is limited by Suggestions.MaxSuggestions parameter (defaults to 20).
* Time is limited by Suggestions.Timeout (defaults to 5000 ms), search depth is limited
* by Suggestions.MaxDepth parameter (default to 4).
*
* @author Tomas Muller
* @version StudentSct 1.3 (Student Sectioning)
* Copyright (C) 2014 Tomas Muller
* [email protected]
* http://muller.unitime.org
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not see http://www.gnu.org/licenses.
*
*/
public class SuggestionsBranchAndBound {
private Hashtable> iRequiredSections = null;
private Set iRequiredFreeTimes = null;
private Hashtable> iPreferredSections = null;
private Request iSelectedRequest = null;
private Section iSelectedSection = null;
private Student iStudent = null;
private TreeSet iSuggestions = new TreeSet();
private int iMaxDepth = 4;
private long iTimeout = 5000;
private int iMaxSuggestions = 20;
private long iT0, iT1;
private boolean iTimeoutReached = false;
private int iNrSolutionsSeen = 0;
private OnlineSectioningModel iModel;
private Assignment iAssignment;
private Hashtable> iValues = new Hashtable>();
private long iLastSuggestionId = 0;
private SuggestionFilter iFilter = null;
protected SelectionComparator iComparator = null;
protected int iMatched = 0;
protected double iMaxSectionsWithPenalty = 0;
/**
* Constructor
* @param properties configuration
* @param student given student
* @param assignment current assignment
* @param requiredSections required sections
* @param requiredFreeTimes required free times (free time requests that must be assigned)
* @param preferredSections preferred sections
* @param selectedRequest selected request
* @param selectedSection selected section
* @param filter section filter
* @param maxSectionsWithPenalty maximal number of sections that have a positive over-expectation penalty {@link OverExpectedCriterion#getOverExpected(Assignment, Section, Request)}
*/
public SuggestionsBranchAndBound(DataProperties properties, Student student,
Assignment assignment, Hashtable> requiredSections,
Set requiredFreeTimes, Hashtable> preferredSections,
Request selectedRequest, Section selectedSection, SuggestionFilter filter, double maxSectionsWithPenalty) {
iRequiredSections = requiredSections;
iRequiredFreeTimes = requiredFreeTimes;
iPreferredSections = preferredSections;
iSelectedRequest = selectedRequest;
iSelectedSection = selectedSection;
iStudent = student;
iModel = (OnlineSectioningModel) selectedRequest.getModel();
iAssignment = assignment;
iMaxDepth = properties.getPropertyInt("Suggestions.MaxDepth", iMaxDepth);
iTimeout = properties.getPropertyLong("Suggestions.Timeout", iTimeout);
iMaxSuggestions = properties.getPropertyInt("Suggestions.MaxSuggestions", iMaxSuggestions);
iMaxSectionsWithPenalty = maxSectionsWithPenalty;
iFilter = filter;
iComparator = new SelectionComparator() {
private HashMap iValues = new HashMap();
private Double value(Enrollment e) {
Double value = iValues.get(e);
if (value == null) {
if (iModel.getStudentQuality() != null)
value = iModel.getStudentWeights().getWeight(iAssignment, e, iModel.getStudentQuality().conflicts(e));
else
value = iModel.getStudentWeights().getWeight(iAssignment, e,
(iModel.getDistanceConflict() == null ? null : iModel.getDistanceConflict().conflicts(e)),
(iModel.getTimeOverlaps() == null ? null : iModel.getTimeOverlaps().conflicts(e)));
iValues.put(e, value);
}
return value;
}
@Override
public int compare(Assignment a, Enrollment e1, Enrollment e2) {
return value(e2).compareTo(value(e1));
}
};
}
/**
* Return search time
* @return search time
*/
public long getTime() {
return iT1 - iT0;
}
/**
* Was time limit reached
* @return true if reached
*/
public boolean isTimeoutReached() {
return iTimeoutReached;
}
/**
* Number of possible suggestions visited
* @return a number of solutions seen
*/
public int getNrSolutionsSeen() {
return iNrSolutionsSeen;
}
/**
* Perform the search
* @return an ordered set of possible suggestions
*/
public TreeSet computeSuggestions() {
iT0 = System.currentTimeMillis();
iTimeoutReached = false;
iNrSolutionsSeen = 0;
iSuggestions.clear();
ArrayList requests2resolve = new ArrayList();
requests2resolve.add(iSelectedRequest);
TreeSet altRequests2resolve = new TreeSet();
for (Map.Entry> entry : iPreferredSections.entrySet()) {
CourseRequest request = entry.getKey();
Set sections = entry.getValue();
if (!sections.isEmpty() && sections.size() == sections.iterator().next().getSubpart().getConfig().getSubparts().size())
iAssignment.assign(0, request.createEnrollment(iAssignment, sections));
else if (!request.equals(iSelectedRequest)) {
if (sections.isEmpty())
altRequests2resolve.add(request);
else
requests2resolve.add(request);
}
}
for (Request request : iStudent.getRequests()) {
if (iAssignment.getValue(request) == null && request instanceof FreeTimeRequest) {
FreeTimeRequest ft = (FreeTimeRequest) request;
Enrollment enrollment = ft.createEnrollment();
if (iModel.conflictValues(iAssignment, enrollment).isEmpty())
iAssignment.assign(0, enrollment);
}
}
for (Request request : iStudent.getRequests()) {
request.setInitialAssignment(iAssignment.getValue(request));
}
backtrack(requests2resolve, altRequests2resolve, 0, iMaxDepth, false);
iT1 = System.currentTimeMillis();
return iSuggestions;
}
/**
* Main branch and bound rutine
* @param requests2resolve remaining requests to assign
* @param altRequests2resolve alternative requests to assign
* @param idx current depth
* @param depth remaining depth
* @param alt can leave a request unassigned
*/
protected void backtrack(ArrayList requests2resolve, TreeSet altRequests2resolve, int idx,
int depth, boolean alt) {
if (!iTimeoutReached && iTimeout > 0 && System.currentTimeMillis() - iT0 > iTimeout)
iTimeoutReached = true;
int nrUnassigned = requests2resolve.size() - idx;
if (nrUnassigned == 0) {
List okFreeTimes = new ArrayList();
double sectionsWithPenalty = 0;
for (Request r : iStudent.getRequests()) {
Enrollment e = iAssignment.getValue(r);
if (iMaxSectionsWithPenalty >= 0 && e != null && r instanceof CourseRequest) {
for (Section s : e.getSections())
sectionsWithPenalty += iModel.getOverExpected(iAssignment, s, r);
}
if (e == null && r instanceof FreeTimeRequest) {
FreeTimeRequest ft = (FreeTimeRequest) r;
Enrollment enrollment = ft.createEnrollment();
if (iModel.conflictValues(iAssignment, enrollment).isEmpty()) {
iAssignment.assign(0, enrollment);
okFreeTimes.add(ft);
}
}
if (e != null && e.isCourseRequest() && e.getSections().isEmpty()) {
Double minPenalty = null;
for (Enrollment other : values(e.getRequest())) {
if (!isAllowed(other)) continue;
if (e.equals(other)) continue;
double penalty = 0.0;
for (Section s: other.getSections())
penalty += iModel.getOverExpected(iAssignment, s, other.getRequest());
if (minPenalty == null || minPenalty > penalty) minPenalty = penalty;
if (minPenalty == 0.0) break;
}
if (minPenalty != null) sectionsWithPenalty += minPenalty;
}
}
if (iMaxSectionsWithPenalty >= 0 && sectionsWithPenalty > iMaxSectionsWithPenalty)
return;
Suggestion s = new Suggestion(requests2resolve);
if (iSuggestions.size() >= iMaxSuggestions && iSuggestions.last().compareTo(s) <= 0)
return;
if (iMatched != 1) {
for (Iterator i = iSuggestions.iterator(); i.hasNext();) {
Suggestion x = i.next();
if (x.sameSelectedSection()) {
if (x.compareTo(s) <= 0) return;
i.remove();
}
}
}
s.init();
iSuggestions.add(s);
if (iSuggestions.size() > iMaxSuggestions)
iSuggestions.remove(iSuggestions.last());
for (FreeTimeRequest ft : okFreeTimes)
iAssignment.unassign(0, ft);
return;
}
if (!canContinue(requests2resolve, idx, depth))
return;
Request request = requests2resolve.get(idx);
for (Enrollment enrollment : values(request)) {
if (!canContinueEvaluation())
break;
if (!isAllowed(enrollment))
continue;
if (enrollment.equals(iAssignment.getValue(request)))
continue;
if (enrollment.getAssignments().isEmpty() && alt)
continue;
Set conflicts = iModel.conflictValues(iAssignment, enrollment);
if (!checkBound(requests2resolve, idx, depth, enrollment, conflicts))
continue;
Enrollment current = iAssignment.getValue(request);
ArrayList newVariables2resolve = new ArrayList(requests2resolve);
for (Iterator i = conflicts.iterator(); i.hasNext();) {
Enrollment conflict = i.next();
iAssignment.unassign(0, conflict.variable());
if (!newVariables2resolve.contains(conflict.variable()))
newVariables2resolve.add(conflict.variable());
}
if (current != null)
iAssignment.unassign(0, current.variable());
iAssignment.assign(0, enrollment);
if (enrollment.getAssignments().isEmpty()) {
if (altRequests2resolve != null && !altRequests2resolve.isEmpty()) {
Suggestion lastBefore = (iSuggestions.isEmpty() ? null : iSuggestions.last());
int sizeBefore = iSuggestions.size();
for (Request r : altRequests2resolve) {
newVariables2resolve.add(r);
backtrack(newVariables2resolve, null, idx + 1, depth, true);
newVariables2resolve.remove(r);
}
Suggestion lastAfter = (iSuggestions.isEmpty() ? null : iSuggestions.last());
int sizeAfter = iSuggestions.size();
// did not succeeded with an alternative -> try without it
if (sizeBefore == sizeAfter && (sizeAfter < iMaxSuggestions || sizeAfter == 0 || lastAfter.compareTo(lastBefore) == 0))
backtrack(newVariables2resolve, altRequests2resolve, idx + 1, depth - 1, alt);
} else {
backtrack(newVariables2resolve, altRequests2resolve, idx + 1, depth - 1, alt);
}
} else {
backtrack(newVariables2resolve, altRequests2resolve, idx + 1, depth - 1, alt);
}
if (current == null)
iAssignment.unassign(0, request);
else
iAssignment.assign(0, current);
for (Iterator i = conflicts.iterator(); i.hasNext();) {
Enrollment conflict = i.next();
iAssignment.assign(0, conflict);
}
}
}
/**
* Domain of a request
* @param request given request
* @return possible enrollments (meeting the filter etc)
*/
protected List values(final Request request) {
if (!request.equals(iSelectedRequest) && !request.getStudent().canAssign(iAssignment, request)) {
if (canLeaveUnassigned(request)) {
List values = new ArrayList();
Config config = null;
if (request instanceof CourseRequest)
config = ((CourseRequest) request).getCourses().get(0).getOffering().getConfigs().get(0);
values.add(new Enrollment(request, 0, config, new HashSet(), iAssignment));
return values;
}
return new ArrayList();
}
List values = iValues.get(request);
if (values != null)
return values;
if (request instanceof CourseRequest) {
CourseRequest cr = (CourseRequest) request;
values = (cr.equals(iSelectedRequest) ? cr.getAvaiableEnrollments(iAssignment) : cr.getAvaiableEnrollmentsSkipSameTime(iAssignment));
if (cr.equals(iSelectedRequest)) {
Collections.sort(values, new Comparator() {
@Override
public int compare(Enrollment e1, Enrollment e2) {
int s1 = 0;
for (Section s: e1.getSections())
if (((CourseRequest)iSelectedRequest).isSelected(s)) s1++;
int s2 = 0;
for (Section s: e2.getSections())
if (((CourseRequest)iSelectedRequest).isSelected(s)) s2++;
if (s1 != s2) return (s1 > s2 ? -1 : 1);
if (e1.getRequest().getInitialAssignment() != null) {
Enrollment original = e1.getRequest().getInitialAssignment();
int x1 = 0;
if (original.getCourse().equals(e1.getCourse())) x1 += 100;
if (original.getConfig().equals(e1.getConfig())) {
x1 += 10;
for (Section section: original.getSections()) {
for (Section s: e1.getSections()) {
if (s.getSubpart().getId() == section.getSubpart().getId()) {
if (ToolBox.equals(section.getTime(), s.getTime()) && ToolBox.equals(section.getRooms(), s.getRooms()))
x1 ++;
break;
}
}
}
}
int x2 = 0;
if (original.getCourse().equals(e2.getCourse())) x2 += 100;
if (original.getConfig().equals(e2.getConfig())) {
x2 += 10;
for (Section section: original.getSections()) {
for (Section s: e2.getSections()) {
if (s.getSubpart().getId() == section.getSubpart().getId()) {
if (ToolBox.equals(section.getTime(), s.getTime()) && ToolBox.equals(section.getRooms(), s.getRooms()))
x2 ++;
break;
}
}
}
}
if (x1 != x2) {
return (x1 > x2 ? -1 : 1);
}
}
return iComparator.compare(iAssignment, e1, e2);
}
});
} else {
Collections.sort(values, new Comparator() {
@Override
public int compare(Enrollment e1, Enrollment e2) {
return iComparator.compare(iAssignment, e1, e2);
}
});
}
} else {
values = new ArrayList();
values.add(((FreeTimeRequest) request).createEnrollment());
}
if (canLeaveUnassigned(request)) {
Config config = null;
if (request instanceof CourseRequest)
config = ((CourseRequest) request).getCourses().get(0).getOffering().getConfigs().get(0);
values.add(new Enrollment(request, 0, config, new HashSet(), iAssignment));
}
iValues.put(request, values);
if (request.equals(iSelectedRequest) && iFilter != null && request instanceof CourseRequest) {
for (Iterator i = values.iterator(); i.hasNext();) {
Enrollment enrollment = i.next();
if (enrollment.getAssignments() != null && !enrollment.getAssignments().isEmpty()) {
boolean match = false;
for (Iterator j = enrollment.getSections().iterator(); j.hasNext();) {
Section section = j.next();
if (iSelectedSection != null) {
if (section.getSubpart().getId() == iSelectedSection.getSubpart().getId()) {
if (iFilter.match(enrollment.getCourse(), section)) {
match = true;
break;
}
}
if (section.getSubpart().getConfig().getId() != iSelectedSection.getSubpart().getConfig().getId() &&
section.getSubpart().getInstructionalType().equals(iSelectedSection.getSubpart().getInstructionalType())) {
if (iFilter.match(enrollment.getCourse(), section)) {
match = true;
break;
}
}
} else {
if (iFilter.match(enrollment.getCourse(), section)) {
match = true;
break;
}
}
}
if (!match)
i.remove();
}
}
}
if (request.equals(iSelectedRequest))
iMatched = values.size();
return values;
}
/**
* Termination criterion
* @param requests2resolve request to resolve
* @param idx current depth
* @param depth remaining depth
* @return true if the search can continue
*/
protected boolean canContinue(ArrayList requests2resolve, int idx, int depth) {
if (depth <= 0)
return false;
if (iTimeoutReached)
return false;
return true;
}
/**
* Can continue evaluation of possible enrollments
* @return false if the timeout was reached
*/
protected boolean canContinueEvaluation() {
return !iTimeoutReached;
}
/**
* Check bound
* @param requests2resolve requests to resolve
* @param idx current depth
* @param depth remaining depth
* @param value enrollment in question
* @param conflicts conflicting enrollments
* @return false if the branch can be cut
*/
protected boolean checkBound(ArrayList requests2resolve, int idx, int depth, Enrollment value,
Set conflicts) {
if (iMaxSectionsWithPenalty < 0.0 && idx > 0 && !conflicts.isEmpty()) return false;
int nrUnassigned = requests2resolve.size() - idx;
if ((nrUnassigned + conflicts.size() > depth)) {
return false;
}
for (Enrollment conflict : conflicts) {
int confIdx = requests2resolve.indexOf(conflict.variable());
if (confIdx >= 0 && confIdx <= idx)
return false;
}
if (iMaxSectionsWithPenalty >= 0) {
double sectionsWithPenalty = 0;
for (Request r : iStudent.getRequests()) {
Enrollment e = iAssignment.getValue(r);
if (r.equals(value.variable())) {
e = value;
} else if (conflicts.contains(e)) {
e = null;
}
if (e != null && e.isCourseRequest()) {
sectionsWithPenalty += iModel.getOverExpected(iAssignment, e, value, conflicts);
}
}
if (sectionsWithPenalty > iMaxSectionsWithPenalty)
return false;
}
return true;
}
/**
* Check required sections
* @param enrollment given enrollment
* @return true if the given enrollment allowed
*/
public boolean isAllowed(Enrollment enrollment) {
if (iRequiredSections != null && enrollment.getRequest() instanceof CourseRequest) {
// Obey required sections
Set required = iRequiredSections.get(enrollment.getRequest());
if (required != null && !required.isEmpty()) {
if (enrollment.getAssignments() == null)
return false;
for (Section r : required)
if (!enrollment.getAssignments().contains(r))
return false;
}
}
if (enrollment.getRequest().equals(iSelectedRequest)) {
// Selected request must be assigned
if (enrollment.getAssignments() == null || enrollment.getAssignments().isEmpty())
return false;
// Selected section must be assigned differently
if (iSelectedSection != null && enrollment.getAssignments().contains(iSelectedSection))
return false;
}
return true;
}
/**
* Can this request be left unassigned
* @param request given request
* @return true if can be left unassigned (there is no requirement)
*/
public boolean canLeaveUnassigned(Request request) {
if (request instanceof CourseRequest) {
if (iRequiredSections != null) {
// Request with required section must be assigned
Set required = iRequiredSections.get(request);
if (required != null && !required.isEmpty())
return false;
}
} else {
// Free time is required
if (iRequiredFreeTimes.contains(request))
return false;
}
// Selected request must be assigned
if (request.equals(iSelectedRequest))
return false;
return true;
}
/**
* Compare two suggestions
* @param assignment current assignment
* @param s1 first suggestion
* @param s2 second suggestion
* @return true if s1 is better than s2
*/
protected int compare(Assignment assignment, Suggestion s1, Suggestion s2) {
return Double.compare(s1.getValue(), s2.getValue());
}
/**
* Suggestion
*/
public class Suggestion implements Comparable {
private double iValue = 0.0;
private int iNrUnassigned = 0;
private int iUnassignedPriority = 0;
private int iNrChanges = 0;
private long iId = iLastSuggestionId++;
private Enrollment[] iEnrollments;
private Section iSelectedEnrollment = null;
private boolean iSelectedEnrollmentChangeTime = false;
private TreeSet iSelectedSections = new TreeSet(new EnrollmentSectionComparator());
private int iSelectedChoice = 0;
/**
* Create suggestion
* @param resolvedRequests assigned requests
*/
public Suggestion(ArrayList resolvedRequests) {
for (Request request : resolvedRequests) {
Enrollment enrollment = iAssignment.getValue(request);
if (enrollment.getAssignments().isEmpty()) {
iNrUnassigned++;
iUnassignedPriority += request.getPriority();
}
iValue += (enrollment.getAssignments() == null || enrollment.getAssignments().isEmpty() ? 0.0 : enrollment.toDouble(iAssignment, false));
if (request.getInitialAssignment() != null && enrollment.isCourseRequest()) {
Enrollment original = request.getInitialAssignment();
for (Iterator i = enrollment.getSections().iterator(); i.hasNext();) {
Section section = i.next();
Section originalSection = null;
for (Iterator j = original.getSections().iterator(); j.hasNext();) {
Section x = j.next();
if (x.getSubpart().getId() == section.getSubpart().getId()) {
originalSection = x;
break;
}
}
if (originalSection == null || !ToolBox.equals(section.getTime(), originalSection.getTime())
|| !ToolBox.equals(section.getRooms(), originalSection.getRooms()))
iNrChanges++;
}
if (!enrollment.getCourse().equals(request.getInitialAssignment().getCourse()))
iNrChanges += 100 * (1 + enrollment.getTruePriority());
if (!enrollment.getConfig().equals(request.getInitialAssignment().getConfig()))
iNrChanges += 10;
}
}
if (iSelectedRequest != null && iSelectedSection != null) {
Enrollment enrollment = iAssignment.getValue(iSelectedRequest);
if (enrollment.getAssignments() != null && !enrollment.getAssignments().isEmpty()) {
for (Iterator i = enrollment.getSections().iterator(); i.hasNext();) {
Section section = i.next();
if (section.getSubpart().getId() == iSelectedSection.getSubpart().getId()) {
iSelectedEnrollment = section;
break;
}
if (section.getSubpart().getConfig().getId() != iSelectedSection.getSubpart().getConfig()
.getId()
&& section.getSubpart().getInstructionalType()
.equals(iSelectedSection.getSubpart().getInstructionalType())) {
iSelectedEnrollment = section;
break;
}
}
}
}
if (iSelectedEnrollment != null)
iSelectedEnrollmentChangeTime = !ToolBox.equals(iSelectedEnrollment.getTime(),
iSelectedSection.getTime());
if (iSelectedRequest != null) {
Enrollment enrollment = iAssignment.getValue(iSelectedRequest);
if (enrollment.isCourseRequest() && enrollment.getAssignments() != null
&& !enrollment.getAssignments().isEmpty()) {
iSelectedSections.addAll(enrollment.getSections());
iSelectedChoice = ((CourseRequest)iSelectedRequest).getCourses().indexOf(enrollment.getCourse());
}
}
}
/** initialization */
public void init() {
iEnrollments = new Enrollment[iStudent.getRequests().size()];
for (int i = 0; i < iStudent.getRequests().size(); i++) {
Request r = iStudent.getRequests().get(i);
iEnrollments[i] = iAssignment.getValue(r);
if (iEnrollments[i] == null) {
Config c = null;
if (r instanceof CourseRequest)
c = ((CourseRequest) r).getCourses().get(0).getOffering().getConfigs().get(0);
iEnrollments[i] = new Enrollment(r, 0, c, null, iAssignment);
}
}
}
/**
* Current assignment for the student
* @return schedule
*/
public Enrollment[] getEnrollments() {
return iEnrollments;
}
/**
* Current value
* @return assignment value
*/
public double getValue() {
return iValue;
}
/**
* Number of unassigned requests
* @return number of unassigned requests
*/
public int getNrUnassigned() {
return iNrUnassigned;
}
/**
* Average unassigned priority
* @return average priority of unassinged requests
*/
public double getAverageUnassignedPriority() {
return ((double) iUnassignedPriority) / iNrUnassigned;
}
/**
* Number of changes in this schedule (comparing to the original one)
* @return number of changes
*/
public int getNrChanges() {
return iNrChanges;
}
/**
* Is the same section selected (as in the current assignment)
* @return true the same section is selected
*/
public boolean sameSelectedSection() {
if (iSelectedRequest != null && iSelectedEnrollment != null) {
Enrollment enrollment = iAssignment.getValue(iSelectedRequest);
if (enrollment != null && enrollment.getAssignments().contains(iSelectedEnrollment))
return true;
if (iSelectedEnrollmentChangeTime && iSelectedSection.getSubpart().getSections().size() > iMaxSuggestions) {
Section selectedEnrollment = null;
for (Iterator i = enrollment.getSections().iterator(); i.hasNext();) {
Section section = i.next();
if (section.getSubpart().getId() == iSelectedSection.getSubpart().getId()) {
selectedEnrollment = section;
break;
}
if (section.getSubpart().getConfig().getId() != iSelectedSection.getSubpart().getConfig().getId() &&
section.getSubpart().getInstructionalType().equals(iSelectedSection.getSubpart().getInstructionalType())) {
selectedEnrollment = section;
break;
}
}
if (selectedEnrollment != null && ToolBox.equals(selectedEnrollment.getTime(), iSelectedEnrollment.getTime()))
return true;
}
}
return false;
}
@Override
public int compareTo(Suggestion suggestion) {
int cmp = Double.compare(getNrUnassigned(), suggestion.getNrUnassigned());
if (cmp != 0)
return cmp;
if (getNrUnassigned() > 0) {
cmp = Double.compare(suggestion.getAverageUnassignedPriority(), getAverageUnassignedPriority());
if (cmp != 0)
return cmp;
}
if (iSelectedRequest != null && iSelectedRequest instanceof CourseRequest) {
int s1 = 0;
for (Section s: iSelectedSections)
if (((CourseRequest)iSelectedRequest).isSelected(s)) s1++;
int s2 = 0;
for (Section s: suggestion.iSelectedSections)
if (((CourseRequest)iSelectedRequest).isSelected(s)) s2++;
if (s1 != s2) {
return (s1 > s2 ? -1 : 1);
}
}
cmp = Double.compare(getNrChanges(), suggestion.getNrChanges());
if (cmp != 0)
return cmp;
cmp = Double.compare(iSelectedChoice, suggestion.iSelectedChoice);
if (cmp != 0)
return cmp;
Iterator i1 = iSelectedSections.iterator();
Iterator i2 = suggestion.iSelectedSections.iterator();
SectionAssignmentComparator c = new SectionAssignmentComparator();
while (i1.hasNext() && i2.hasNext()) {
cmp = c.compare(i1.next(), i2.next());
if (cmp != 0)
return cmp;
}
cmp = compare(iAssignment, this, suggestion);
if (cmp != 0)
return cmp;
return Double.compare(iId, suggestion.iId);
}
}
/**
* Enrollment comparator (used to sort enrollments in a domain).
* Selected sections go first.
*/
public class EnrollmentSectionComparator implements Comparator {
/**
* Is section s1 parent of section s2 (or a parent of a parent...)
* @param s1 a section
* @param s2 a section
* @return if there is a parent-child relation between the two sections
*/
public boolean isParent(Section s1, Section s2) {
Section p1 = s1.getParent();
if (p1 == null)
return false;
if (p1.equals(s2))
return true;
return isParent(p1, s2);
}
@Override
public int compare(Section a, Section b) {
if (iSelectedSection != null && iSelectedSection.getSubpart().getId() == a.getSubpart().getId())
return -1;
if (iSelectedSection != null && iSelectedSection.getSubpart().getId() == b.getSubpart().getId())
return 1;
if (isParent(a, b))
return 1;
if (isParent(b, a))
return -1;
int cmp = a.getSubpart().getInstructionalType().compareToIgnoreCase(b.getSubpart().getInstructionalType());
if (cmp != 0)
return cmp;
return Double.compare(a.getId(), b.getId());
}
}
/**
* Section comparator. Order section by time first, then by room.
*/
public class SectionAssignmentComparator implements Comparator {
@Override
public int compare(Section a, Section b) {
TimeLocation t1 = (a == null ? null : a.getTime());
TimeLocation t2 = (b == null ? null : b.getTime());
if (t1 != null && t2 != null) {
for (int i = 0; i < Constants.DAY_CODES.length; i++) {
if ((t1.getDayCode() & Constants.DAY_CODES[i]) != 0) {
if ((t2.getDayCode() & Constants.DAY_CODES[i]) == 0)
return -1;
} else if ((t2.getDayCode() & Constants.DAY_CODES[i]) != 0) {
return 1;
}
}
int cmp = Double.compare(t1.getStartSlot(), t2.getStartSlot());
if (cmp != 0)
return cmp;
}
String r1 = (a == null || a.getRooms() == null ? null : a.getRooms().toString());
String r2 = (b == null || b.getRooms() == null ? null : b.getRooms().toString());
if (r1 != null && r2 != null) {
int cmp = r1.compareToIgnoreCase(r2);
if (cmp != 0)
return cmp;
}
return 0;
}
}
/**
* Number of possible enrollments of the selected request
* @return number of possible enrollment of the selected request (meeting the given filter etc.)
*/
public int getNrMatched() {
return iMatched;
}
/**
* Suggestion filter.
*/
public static interface SuggestionFilter {
/**
* Match the given section
* @param course given course
* @param section given section
* @return true if matching the filter (can be used)
*/
public boolean match(Course course, Section section);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy