001package org.cpsolver.studentsct.model; 002 003import java.util.ArrayList; 004import java.util.HashSet; 005import java.util.List; 006import java.util.Set; 007 008import org.cpsolver.coursett.model.TimeLocation; 009import org.cpsolver.ifs.assignment.Assignment; 010import org.cpsolver.studentsct.constraint.LinkedSections; 011import org.cpsolver.studentsct.model.Request.RequestPriority; 012 013 014 015/** 016 * Representation of a student. Each student contains id, and a list of 017 * requests. <br> 018 * <br> 019 * Last-like semester students are mark as dummy. Dummy students have lower 020 * value and generally should not block "real" students from getting requested 021 * courses. <br> 022 * <br> 023 * 024 * @version StudentSct 1.3 (Student Sectioning)<br> 025 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 026 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 027 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 028 * <br> 029 * This library is free software; you can redistribute it and/or modify 030 * it under the terms of the GNU Lesser General Public License as 031 * published by the Free Software Foundation; either version 3 of the 032 * License, or (at your option) any later version. <br> 033 * <br> 034 * This library is distributed in the hope that it will be useful, but 035 * WITHOUT ANY WARRANTY; without even the implied warranty of 036 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 037 * Lesser General Public License for more details. <br> 038 * <br> 039 * You should have received a copy of the GNU Lesser General Public 040 * License along with this library; if not see 041 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 042 */ 043public class Student implements Comparable<Student> { 044 private long iId; 045 private String iExternalId = null, iName = null; 046 private StudentPriority iPriority = StudentPriority.Normal; 047 private List<Request> iRequests = new ArrayList<Request>(); 048 private List<AreaClassificationMajor> iMajors = new ArrayList<AreaClassificationMajor>(); 049 private List<AreaClassificationMajor> iMinors = new ArrayList<AreaClassificationMajor>(); 050 private List<LinkedSections> iLinkedSections = new ArrayList<LinkedSections>(); 051 private Set<String> iAccommodations = new HashSet<String>(); 052 private List<StudentGroup> iGroups = new ArrayList<StudentGroup>(); 053 private String iStatus = null; 054 private Long iEmailTimeStamp = null; 055 private List<Unavailability> iUnavailabilities = new ArrayList<Unavailability>(); 056 private boolean iNeedShortDistances = false; 057 private boolean iAllowDisabled = false; 058 private Float iMinCredit = null; 059 private Float iMaxCredit = null; 060 private List<Instructor> iAdvisors = new ArrayList<Instructor>(); 061 062 /** 063 * Constructor 064 * 065 * @param id 066 * student unique id 067 */ 068 public Student(long id) { 069 iId = id; 070 } 071 072 /** 073 * Constructor 074 * 075 * @param id 076 * student unique id 077 * @param dummy 078 * dummy flag 079 */ 080 public Student(long id, boolean dummy) { 081 iId = id; 082 iPriority = (dummy ? StudentPriority.Dummy : StudentPriority.Normal); 083 } 084 085 /** Student unique id 086 * @return student unique id 087 **/ 088 public long getId() { 089 return iId; 090 } 091 092 /** Set student unique id 093 * @param id student unique id 094 **/ 095 public void setId(long id) { 096 iId = id; 097 } 098 099 /** Student's course and free time requests 100 * @return student requests 101 **/ 102 public List<Request> getRequests() { 103 return iRequests; 104 } 105 106 /** Number of requests (alternative requests are ignored) 107 * @return number of non alternative student requests 108 **/ 109 public int nrRequests() { 110 int ret = 0; 111 for (Request r : getRequests()) { 112 if (!r.isAlternative()) 113 ret++; 114 } 115 return ret; 116 } 117 118 /** Number of alternative requests 119 * @return number of alternative student requests 120 **/ 121 public int nrAlternativeRequests() { 122 int ret = 0; 123 for (Request r : getRequests()) { 124 if (r.isAlternative()) 125 ret++; 126 } 127 return ret; 128 } 129 130 /** 131 * True if the given request can be assigned to the student. A request 132 * cannot be assigned to a student when the student already has the desired 133 * number of requests assigned (i.e., number of non-alternative course 134 * requests). 135 * @param assignment current assignment 136 * @param request given request of this student 137 * @return true if the given request can be assigned 138 **/ 139 public boolean canAssign(Assignment<Request, Enrollment> assignment, Request request) { 140 if (request.isAssigned(assignment)) 141 return true; 142 int alt = 0; 143 float credit = 0f; 144 boolean found = false; 145 for (Request r : getRequests()) { 146 if (r.equals(request)) 147 found = true; 148 boolean assigned = (r.isAssigned(assignment) || r.equals(request)); 149 boolean course = (r instanceof CourseRequest); 150 boolean waitlist = (course && ((CourseRequest) r).isWaitlist()); 151 if (r.isAlternative()) { 152 if (assigned || (!found && waitlist)) 153 alt--; 154 } else { 155 if (course && !waitlist && !assigned) 156 alt++; 157 } 158 if (r.equals(request)) 159 credit += r.getMinCredit(); 160 else { 161 Enrollment e = r.getAssignment(assignment); 162 if (e != null) credit += e.getCredit(); 163 } 164 } 165 return (alt >= 0 && credit <= getMaxCredit()); 166 } 167 168 /** 169 * True if the student has assigned the desired number of requests (i.e., 170 * number of non-alternative course requests). 171 * @param assignment current assignment 172 * @return true if this student has a complete schedule 173 */ 174 public boolean isComplete(Assignment<Request, Enrollment> assignment) { 175 int nrRequests = 0; 176 int nrAssignedRequests = 0; 177 float credit = 0f; 178 Float minCredit = null; 179 for (Request r : getRequests()) { 180 if (!(r instanceof CourseRequest)) 181 continue; // ignore free times 182 if (!r.isAlternative()) 183 nrRequests++; 184 if (r.isAssigned(assignment)) 185 nrAssignedRequests++; 186 Enrollment e = r.getAssignment(assignment); 187 if (e != null) { 188 credit += e.getCredit(); 189 } else if (r instanceof CourseRequest) { 190 minCredit = (minCredit == null ? r.getMinCredit() : Math.min(minCredit, r.getMinCredit())); 191 } 192 } 193 return nrAssignedRequests == nrRequests || credit + (minCredit == null ? 0f : minCredit.floatValue()) > getMaxCredit(); 194 } 195 196 /** Number of assigned COURSE requests 197 * @param assignment current assignment 198 * @return number of assigned course requests 199 **/ 200 public int nrAssignedRequests(Assignment<Request, Enrollment> assignment) { 201 int nrAssignedRequests = 0; 202 for (Request r : getRequests()) { 203 if (!(r instanceof CourseRequest)) 204 continue; // ignore free times 205 if (r.isAssigned(assignment)) 206 nrAssignedRequests++; 207 } 208 return nrAssignedRequests; 209 } 210 211 @Override 212 public String toString() { 213 return (isDummy() ? "D" : "") + "S[" + getId() + "]"; 214 } 215 216 /** 217 * Student's dummy flag. Dummy students have lower value and generally 218 * should not block "real" students from getting requested courses. 219 * @return true if projected student 220 */ 221 public boolean isDummy() { 222 return iPriority == StudentPriority.Dummy; 223 } 224 225 /** 226 * Set student's dummy flag. Dummy students have lower value and generally 227 * should not block "real" students from getting requested courses. 228 * @param dummy projected student 229 */ 230 public void setDummy(boolean dummy) { 231 if (dummy) 232 iPriority = StudentPriority.Dummy; 233 else if (iPriority == StudentPriority.Dummy) 234 iPriority = StudentPriority.Normal; 235 } 236 237 /** 238 * Student's priority. Priority students are to be assigned first. 239 * @return student priority level 240 */ 241 public StudentPriority getPriority() { 242 return iPriority; 243 } 244 245 /** 246 * Set student's priority. Priority students are to be assigned first. 247 * @param priority student priority level 248 */ 249 public void setPriority(StudentPriority priority) { 250 iPriority = priority; 251 } 252 253 /** 254 * Set student's priority. Priority students are to be assigned first. 255 * @param priority true for priority student 256 */ 257 @Deprecated 258 public void setPriority(boolean priority) { 259 if (priority) 260 iPriority = StudentPriority.Priority; 261 else if (StudentPriority.Normal.isHigher(this)) 262 iPriority = StudentPriority.Normal; 263 } 264 265 /** 266 * Student's priority. Priority students are to be assigned first. 267 * @return true if priority student 268 */ 269 @Deprecated 270 public boolean isPriority() { 271 return StudentPriority.Normal.isHigher(this); 272 } 273 274 275 /** 276 * List of student groups ({@link StudentGroup}) for the given student 277 * @return list of academic area abbreviation (group type) & group code pairs 278 */ 279 public List<StudentGroup> getGroups() { 280 return iGroups; 281 } 282 283 /** 284 * List student accommodations 285 * @return student accommodations 286 */ 287 public Set<String> getAccommodations() { 288 return iAccommodations; 289 } 290 291 /** 292 * List of academic area, classification, and major codes ({@link AreaClassificationMajor}) for the given student 293 * @return list of academic area, classification, and major codes 294 */ 295 public List<AreaClassificationMajor> getAreaClassificationMajors() { 296 return iMajors; 297 } 298 299 /** 300 * List of academic area, classification, and minor codes ({@link AreaClassificationMajor}) for the given student 301 * @return list of academic area, classification, and minor codes 302 */ 303 public List<AreaClassificationMajor> getAreaClassificationMinors() { 304 return iMinors; 305 } 306 307 /** 308 * List of student's advisors 309 */ 310 public List<Instructor> getAdvisors() { 311 return iAdvisors; 312 } 313 314 /** 315 * Compare two students for equality. Two students are considered equal if 316 * they have the same id. 317 */ 318 @Override 319 public boolean equals(Object object) { 320 if (object == null || !(object instanceof Student)) 321 return false; 322 return getId() == ((Student) object).getId() && isDummy() == ((Student) object).isDummy(); 323 } 324 325 /** 326 * Hash code (base only on student id) 327 */ 328 @Override 329 public int hashCode() { 330 return (int) (iId ^ (iId >>> 32)); 331 } 332 333 /** 334 * Count number of free time slots overlapping with the given enrollment 335 * @param enrollment given enrollment 336 * @return number of slots overlapping with a free time request 337 */ 338 public int countFreeTimeOverlaps(Enrollment enrollment) { 339 if (!enrollment.isCourseRequest()) return 0; 340 int ret = 0; 341 for (Section section: enrollment.getSections()) { 342 TimeLocation time = section.getTime(); 343 if (time != null) 344 ret += countFreeTimeOverlaps(time); 345 } 346 return ret; 347 } 348 349 /** 350 * Count number of free time slots overlapping with the given time 351 * @param time given time 352 * @return number of time slots overlapping with a free time request 353 */ 354 public int countFreeTimeOverlaps(TimeLocation time) { 355 int ret = 0; 356 for (Request r: iRequests) { 357 if (r instanceof FreeTimeRequest) { 358 TimeLocation freeTime = ((FreeTimeRequest)r).getTime(); 359 if (time.hasIntersection(freeTime)) 360 ret += freeTime.nrSharedHours(time) * freeTime.nrSharedDays(time); 361 } 362 } 363 return ret; 364 } 365 366 /** 367 * Get student external id 368 * @return student external unique id 369 */ 370 public String getExternalId() { return iExternalId; } 371 /** 372 * Set student external id 373 * @param externalId student external id 374 */ 375 public void setExternalId(String externalId) { iExternalId = externalId; } 376 377 /** 378 * Get student name 379 * @return student name 380 */ 381 public String getName() { return iName; } 382 /** 383 * Set student name 384 * @param name student name 385 */ 386 public void setName(String name) { iName = name; } 387 388 /** 389 * Linked sections of this student 390 * @return linked sections of this student 391 */ 392 public List<LinkedSections> getLinkedSections() { return iLinkedSections; } 393 394 /** 395 * Get student status (online sectioning only) 396 * @return student sectioning status 397 */ 398 public String getStatus() { return iStatus; } 399 /** 400 * Set student status 401 * @param status student sectioning status 402 */ 403 public void setStatus(String status) { iStatus = status; } 404 405 /** 406 * Get last email time stamp (online sectioning only) 407 * @return student email time stamp 408 */ 409 public Long getEmailTimeStamp() { return iEmailTimeStamp; } 410 /** 411 * Set last email time stamp 412 * @param emailTimeStamp student email time stamp 413 */ 414 public void setEmailTimeStamp(Long emailTimeStamp) { iEmailTimeStamp = emailTimeStamp; } 415 416 @Override 417 public int compareTo(Student s) { 418 // priority students first, dummy students last 419 if (getPriority() != s.getPriority()) 420 return (getPriority().ordinal() < s.getPriority().ordinal() ? -1 : 1); 421 // then id 422 return new Long(getId()).compareTo(s.getId()); 423 } 424 425 /** 426 * List of student unavailabilities 427 * @return student unavailabilities 428 */ 429 public List<Unavailability> getUnavailabilities() { return iUnavailabilities; } 430 431 /** 432 * Check if student is available during the given section 433 * @param section given section 434 * @return true, if available (the section cannot overlap and there is no overlapping unavailability that cannot overlap) 435 */ 436 public boolean isAvailable(Section section) { 437 if (section.isAllowOverlap() || section.getTime() == null) return true; 438 for (Unavailability unavailability: getUnavailabilities()) 439 if (unavailability.isOverlapping(section)) return false; 440 return true; 441 } 442 443 /** 444 * Check if student is available during the given enrollment 445 * @param enrollment given enrollment 446 * @return true, if available 447 */ 448 public boolean isAvailable(Enrollment enrollment) { 449 if (enrollment != null && enrollment.isCourseRequest() && !enrollment.isAllowOverlap()) 450 for (Section section: enrollment.getSections()) 451 if (!isAvailable(section)) return false; 452 return true; 453 } 454 455 /** 456 * Return true if the student needs short distances. A different distance conflict checking is employed for such students. 457 * @return true if the student needs short distances 458 */ 459 public boolean isNeedShortDistances() { 460 return iNeedShortDistances; 461 } 462 463 /** 464 * Set true if the student needs short distances. A different distance conflict checking is employed for such students. 465 * @param needShortDistances true if the student needs short distances (default is false) 466 */ 467 public void setNeedShortDistances(boolean needShortDistances) { 468 iNeedShortDistances = needShortDistances; 469 } 470 471 /** 472 * True if student can be enrolled in disabled sections, regardless if his/her reservations 473 * @return does this student allow for disabled sections 474 */ 475 public boolean isAllowDisabled() { 476 return iAllowDisabled; 477 } 478 479 /** 480 * Set to true if student can be enrolled in disabled sections, regardless if his/her reservations 481 * @param allowDisabled does this student allow for disabled sections 482 */ 483 public void setAllowDisabled(boolean allowDisabled) { 484 iAllowDisabled = allowDisabled; 485 } 486 487 /** 488 * True if student has min credit defined 489 * @return true if min credit is set 490 */ 491 public boolean hasMinCredit() { return iMinCredit != null; } 492 493 /** 494 * Get student min credit (0 if not set) 495 * return student min credit 496 */ 497 public float getMinCredit() { return (iMinCredit == null ? 0 : iMinCredit.floatValue()); } 498 499 /** 500 * Has student any critical course requests? 501 * @return true if a student has at least one course request that is marked as critical 502 */ 503 @Deprecated 504 public boolean hasCritical() { 505 for (Request r: iRequests) 506 if (!r.isAlternative() && r.isCritical()) return true; 507 return false; 508 } 509 510 /** 511 * Has student any critical course requests? 512 * @return true if a student has at least one course request that is marked as critical 513 */ 514 public boolean hasCritical(RequestPriority rp) { 515 for (Request r: iRequests) 516 if (!r.isAlternative() && rp.isCritical(r)) return true; 517 return false; 518 } 519 520 /** 521 * Has student any unassigned critical course requests? 522 * @return true if a student has at least one not-alternative course request that is marked as critical and that is not assigned 523 */ 524 @Deprecated 525 public boolean hasUnassignedCritical(Assignment<Request, Enrollment> assignment) { 526 for (Request r: iRequests) 527 if (!r.isAlternative() && r.isCritical() && assignment.getValue(r) == null) return true; 528 return false; 529 } 530 531 /** 532 * Has student any unassigned critical course requests? 533 * @return true if a student has at least one not-alternative course request that is marked as critical and that is not assigned 534 */ 535 public boolean hasUnassignedCritical(Assignment<Request, Enrollment> assignment, RequestPriority rp) { 536 for (Request r: iRequests) 537 if (!r.isAlternative() && rp.isCritical(r) && assignment.getValue(r) == null) return true; 538 return false; 539 } 540 541 /** 542 * Set student min credit (null if not set) 543 * @param maxCredit student min credit 544 */ 545 public void setMinCredit(Float maxCredit) { iMinCredit = maxCredit; } 546 547 /** 548 * True if student has max credit defined 549 * @return true if max credit is set 550 */ 551 public boolean hasMaxCredit() { return iMaxCredit != null; } 552 553 /** 554 * Get student max credit ({@link Float#MAX_VALUE} if not set) 555 * return student max credit 556 */ 557 public float getMaxCredit() { return (iMaxCredit == null ? Float.MAX_VALUE : iMaxCredit.floatValue()); } 558 559 /** 560 * Set student max credit (null if not set) 561 * @param maxCredit student max credit 562 */ 563 public void setMaxCredit(Float maxCredit) { iMaxCredit = maxCredit; } 564 565 /** 566 * Return the number of assigned credits of the student 567 * @param assignment current assignment 568 * @return total assigned credit using {@link Enrollment#getCredit()} 569 */ 570 public float getAssignedCredit(Assignment<Request, Enrollment> assignment) { 571 float credit = 0f; 572 for (Request r: getRequests()) { 573 Enrollment e = r.getAssignment(assignment); 574 if (e != null) credit += e.getCredit(); 575 } 576 return credit; 577 } 578 579 /** 580 * Student priority level. Higher priority students are to be assigned first. 581 * The student priority is used to re-order students and assign them accoding 582 * to their priority. 583 */ 584 public static enum StudentPriority { 585 Priority("P", 1.00), 586 Senior("4", 0.70), 587 Junior("3", 0.49), 588 Sophomore("2", 0.33), 589 Freshmen("1", 0.24), 590 Normal("N", null), // this is the default priority 591 Dummy("D", null), // dummy students priority 592 ; 593 594 String iCode; 595 Double iBoost; 596 StudentPriority(String code, Double boost) { 597 iCode = code; 598 iBoost = boost; 599 } 600 public String code() { return iCode; } 601 public Double getBoost() { return iBoost; } 602 603 public boolean isSameOrHigher(Student s) { 604 return s.getPriority().ordinal() <= ordinal(); 605 } 606 public boolean isHigher(Student s) { 607 return ordinal() < s.getPriority().ordinal(); 608 } 609 public boolean isSame(Student s) { 610 return ordinal() == s.getPriority().ordinal(); 611 } 612 public static StudentPriority getPriority(String value) { 613 if ("true".equalsIgnoreCase(value)) return StudentPriority.Priority; 614 if ("false".equalsIgnoreCase(value)) return StudentPriority.Normal; 615 for (StudentPriority sp: StudentPriority.values()) { 616 if (sp.name().equalsIgnoreCase(value)) return sp; 617 } 618 return StudentPriority.Normal; 619 } 620 } 621 622 /** 623 * Check if a student has given accommodation 624 * @param code accommodation reference code 625 * @return true if present 626 */ 627 public boolean hasAccommodation(String code) { 628 return code != null && !code.isEmpty() && iAccommodations.contains(code); 629 } 630}