001package org.cpsolver.coursett.model; 002 003import java.util.BitSet; 004import java.util.Enumeration; 005 006import org.cpsolver.coursett.Constants; 007import org.cpsolver.ifs.util.ToolBox; 008 009 010/** 011 * Time part of placement. 012 * 013 * @author Tomáš Müller 014 * @version CourseTT 1.3 (University Course Timetabling)<br> 015 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 016 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 017 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 018 * <br> 019 * This library is free software; you can redistribute it and/or modify 020 * it under the terms of the GNU Lesser General Public License as 021 * published by the Free Software Foundation; either version 3 of the 022 * License, or (at your option) any later version. <br> 023 * <br> 024 * This library is distributed in the hope that it will be useful, but 025 * WITHOUT ANY WARRANTY; without even the implied warranty of 026 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 027 * Lesser General Public License for more details. <br> 028 * <br> 029 * You should have received a copy of the GNU Lesser General Public 030 * License along with this library; if not see 031 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 032 */ 033 034public class TimeLocation { 035 private int iStartSlot; 036 037 private int iPreference; 038 private double iNormalizedPreference; 039 040 private Long iTimePatternId = null; 041 private int iHashCode; 042 043 private int iDayCode; 044 private int iLength; 045 private int iNrMeetings; 046 private int iBreakTime; 047 048 private BitSet iWeekCode; 049 private Long iDatePatternId = null; 050 private String iDatePatternName = null; 051 private int iDatePreference; 052 053 /** 054 * Constructor 055 * 056 * @param dayCode 057 * days (combination of 1 for Monday, 2 for Tuesday, ...) 058 * @param startTime 059 * start slot 060 * @param length 061 * number of slots 062 * @param pref 063 * time preference 064 * @param normPref normalized preference 065 * @param datePatternPreference date pattern preference 066 * @param datePatternId date pattern unique id 067 * @param datePatternName date pattern name 068 * @param weekCode date pattern (binary string with 1 for each day when classes take place) 069 * @param breakTime break time in minutes 070 */ 071 public TimeLocation(int dayCode, int startTime, int length, int pref, double normPref, int datePatternPreference, 072 Long datePatternId, String datePatternName, BitSet weekCode, int breakTime) { 073 iPreference = pref; 074 iNormalizedPreference = normPref; 075 iStartSlot = startTime; 076 iDayCode = dayCode; 077 iLength = length; 078 iBreakTime = breakTime; 079 iNrMeetings = 0; 080 for (int i = 0; i < Constants.DAY_CODES.length; i++) { 081 if ((iDayCode & Constants.DAY_CODES[i]) == 0) 082 continue; 083 iNrMeetings++; 084 } 085 iHashCode = combine(combine(iDayCode, iStartSlot), iLength); 086 iDatePatternName = datePatternName; 087 iWeekCode = weekCode; 088 iDatePatternId = datePatternId; 089 if (iDatePatternName == null) 090 iDatePatternName = "not set"; 091 iDatePreference = datePatternPreference; 092 if (iWeekCode == null) { 093 iWeekCode = new BitSet(366); 094 for (int i = 0; i <= 365; i++) 095 iWeekCode.set(i); 096 } 097 } 098 099 public TimeLocation(int dayCode, int startTime, int length, int pref, double normPref, Long datePatternId, 100 String datePatternName, BitSet weekCode, int breakTime) { 101 this(dayCode, startTime, length, pref, normPref, 0, datePatternId, datePatternName, weekCode, breakTime); 102 } 103 104 /** Number of meetings 105 * @return number of meetings 106 **/ 107 public int getNrMeetings() { 108 return iNrMeetings; 109 } 110 111 public int getBreakTime() { 112 return iBreakTime; 113 } 114 115 public void setBreakTime(int breakTime) { 116 iBreakTime = breakTime; 117 } 118 119 private static int combine(int a, int b) { 120 int ret = 0; 121 for (int i = 0; i < 15; i++) 122 ret = ret | ((a & (1 << i)) << i) | ((b & (1 << i)) << (i + 1)); 123 return ret; 124 } 125 126 /** Days (combination of 1 for Monday, 2 for Tuesday, ...) 127 * @return days of the week of this time 128 **/ 129 public int getDayCode() { 130 return iDayCode; 131 } 132 133 /** Days for printing purposes 134 * @return day header (e.g., MWF for Monday - Wednesday - Friday time) 135 **/ 136 public String getDayHeader() { 137 StringBuffer sb = new StringBuffer(); 138 for (int i = 0; i < Constants.DAY_CODES.length; i++) 139 if ((iDayCode & Constants.DAY_CODES[i]) != 0) 140 sb.append(Constants.DAY_NAMES_SHORT[i]); 141 return sb.toString(); 142 } 143 144 /** Start time for printing purposes 145 * @param useAmPm use 12-hour format 146 * @return time header (e.g., 7:30a) 147 **/ 148 public String getStartTimeHeader(boolean useAmPm) { 149 int min = iStartSlot * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN; 150 int h = min / 60; 151 int m = min % 60; 152 if (useAmPm) 153 return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a"); 154 else 155 return h + ":" + (m < 10 ? "0" : "") + m; 156 } 157 158 /** Start time for printing purposes 159 * @return time header (e.g., 7:30a) 160 **/ 161 @Deprecated 162 public String getStartTimeHeader() { 163 return getStartTimeHeader(true); 164 } 165 166 /** End time for printing purposes 167 * @param useAmPm use 12-hour format 168 * @return end time (e.g., 8:20a) 169 **/ 170 public String getEndTimeHeader(boolean useAmPm) { 171 int min = (iStartSlot + iLength) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN - getBreakTime(); 172 int m = min % 60; 173 int h = min / 60; 174 if (useAmPm) 175 return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a"); 176 else 177 return h + ":" + (m < 10 ? "0" : "") + m; 178 } 179 180 /** End time for printing purposes 181 * @return end time (e.g., 8:20a) 182 **/ 183 @Deprecated 184 public String getEndTimeHeader() { 185 return getEndTimeHeader(true); 186 } 187 188 189 /** End time for printing purposes 190 * @param useAmPm use 12-hour format 191 * @return end time not counting break time (e.g., 8:30a) 192 **/ 193 public String getEndTimeHeaderNoAdj(boolean useAmPm) { 194 int min = (iStartSlot + iLength) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN; 195 int m = min % 60; 196 int h = min / 60; 197 if (useAmPm) 198 return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a"); 199 else 200 return h + ":" + (m < 10 ? "0" : "") + m; 201 } 202 203 /** End time for printing purposes 204 * @return end time not counting break time (e.g., 8:30a) 205 **/ 206 @Deprecated 207 public String getEndTimeHeaderNoAdj() { 208 return getEndTimeHeaderNoAdj(true); 209 } 210 211 /** Start slot 212 * @return start slot 213 **/ 214 public int getStartSlot() { 215 return iStartSlot; 216 } 217 218 /** Used slots in a day (combination of 1..first, 2..second,...) */ 219 220 /** true if days overlap 221 * @param anotherLocation another time 222 * @return true if days of the week overlaps 223 **/ 224 public boolean shareDays(TimeLocation anotherLocation) { 225 return ((iDayCode & anotherLocation.iDayCode) != 0); 226 } 227 228 /** number of overlapping days 229 * @param anotherLocation another time 230 * @return number of days of the week that the two times share 231 **/ 232 public int nrSharedDays(TimeLocation anotherLocation) { 233 int ret = 0; 234 for (int i = 0; i < Constants.NR_DAYS; i++) { 235 if ((iDayCode & Constants.DAY_CODES[i]) == 0) 236 continue; 237 if ((anotherLocation.iDayCode & Constants.DAY_CODES[i]) == 0) 238 continue; 239 ret++; 240 } 241 return ret; 242 } 243 244 /** true if hours overlap 245 * @param anotherLocation another time 246 * @return true if the two times overlap in time (just the time of the day is considered) 247 **/ 248 public boolean shareHours(TimeLocation anotherLocation) { 249 return (iStartSlot + iLength > anotherLocation.iStartSlot) 250 && (anotherLocation.iStartSlot + anotherLocation.iLength > iStartSlot); 251 } 252 253 /** number of overlapping time slots (ignoring days) 254 * @param anotherLocation another time 255 * @return number of time slots the two location overlap 256 **/ 257 public int nrSharedHours(TimeLocation anotherLocation) { 258 int end = Math.min(iStartSlot + iLength, anotherLocation.iStartSlot + anotherLocation.iLength); 259 int start = Math.max(iStartSlot, anotherLocation.iStartSlot); 260 return (end < start ? 0 : end - start); 261 } 262 263 /** true if weeks overlap 264 * @param anotherLocation another time 265 * @return true if the date patterns overlap 266 */ 267 public boolean shareWeeks(TimeLocation anotherLocation) { 268 return iWeekCode.intersects(anotherLocation.iWeekCode); 269 } 270 271 /** true if weeks overlap 272 * @param weekCode another date pattern 273 * @return true if the date patterns overlap 274 */ 275 public boolean shareWeeks(BitSet weekCode) { 276 return iWeekCode.intersects(weekCode); 277 } 278 279 public boolean hasDay(int day) { 280 return iWeekCode.get(day); 281 } 282 283 /** true if overlap 284 * @param anotherLocation another time 285 * @return true if the two times overlap, this means that all three checks {@link TimeLocation#shareDays(TimeLocation)}, {@link TimeLocation#shareHours(TimeLocation)} and {@link TimeLocation#shareWeeks(TimeLocation)} are true. 286 **/ 287 public boolean hasIntersection(TimeLocation anotherLocation) { 288 return shareDays(anotherLocation) && shareHours(anotherLocation) && shareWeeks(anotherLocation); 289 } 290 291 /** Used slots 292 * @return enumeration of used slots 293 **/ 294 public IntEnumeration getSlots() { 295 return new SlotsEnum(); 296 } 297 298 /** Used start slots (for each meeting) 299 * @return enumeration of start slots for each meeting of the time 300 **/ 301 public IntEnumeration getStartSlots() { 302 return new StartSlotsEnum(); 303 } 304 305 /** Days 306 * @return enumeration of days of week of the time 307 **/ 308 public IntEnumeration getDays() { 309 return new DaysEnum(); 310 } 311 312 private int[] iDaysCache = null; 313 public int[] getDaysArray() { 314 if (iDaysCache == null) { 315 iDaysCache = new int[getNrMeetings()]; 316 int i = 0; 317 for (Enumeration<Integer> e = getDays(); e.hasMoreElements();) 318 iDaysCache[i++] = e.nextElement(); 319 } 320 return iDaysCache; 321 } 322 323 /** Text representation 324 * @param useAmPm 12-hour format 325 * @return time name (e.g., MWF 7:30a) 326 **/ 327 public String getName(boolean useAmPm) { 328 return getDayHeader() + " " + getStartTimeHeader(useAmPm); 329 } 330 331 @Deprecated 332 public String getName() { 333 return getName(true); 334 } 335 336 public String getLongName(boolean useAmPm) { 337 return getDayHeader() + " " + getStartTimeHeader(useAmPm) + " - " + getEndTimeHeader(useAmPm) + " " + getDatePatternName(); 338 } 339 340 @Deprecated 341 public String getLongName() { 342 return getLongName(true); 343 } 344 345 public String getLongNameNoAdj(boolean useAmPm) { 346 return getDayHeader() + " " + getStartTimeHeader(useAmPm) + " - " + getEndTimeHeaderNoAdj(useAmPm) + " " + getDatePatternName(); 347 } 348 349 public String getLongNameNoAdj() { 350 return getLongNameNoAdj(true); 351 } 352 353 /** Preference 354 * @return time preference 355 **/ 356 public int getPreference() { 357 return iPreference; 358 } 359 360 public void setPreference(int preference) { 361 iPreference = preference; 362 } 363 364 /** Length 365 * @return time length (in the number of slots) 366 **/ 367 public int getLength() { 368 return iLength; 369 } 370 371 /** Length 372 * @return time length (in the number of slots) 373 **/ 374 public int getNrSlotsPerMeeting() { 375 return iLength; 376 } 377 378 /** Normalized preference 379 * @return normalized preference 380 **/ 381 public double getNormalizedPreference() { 382 return iNormalizedPreference; 383 } 384 385 public void setNormalizedPreference(double normalizedPreference) { 386 iNormalizedPreference = normalizedPreference; 387 } 388 389 /** Time pattern model (can be null) 390 * @return time pattern unique id 391 **/ 392 public Long getTimePatternId() { 393 return iTimePatternId; 394 } 395 396 public Long getDatePatternId() { 397 return iDatePatternId; 398 } 399 400 public void setTimePatternId(Long timePatternId) { 401 iTimePatternId = timePatternId; 402 } 403 404 public BitSet getWeekCode() { 405 return iWeekCode; 406 } 407 408 public String getDatePatternName() { 409 return iDatePatternName; 410 } 411 412 public void setDatePattern(Long datePatternId, String datePatternName, BitSet weekCode) { 413 iDatePatternId = datePatternId; 414 iDatePatternName = datePatternName; 415 iWeekCode = weekCode; 416 } 417 418 public int getDatePatternPreference() { 419 return iDatePreference; 420 } 421 422 @Override 423 public String toString() { 424 return getName() + " (" + iNormalizedPreference + ")"; 425 } 426 427 @Override 428 public int hashCode() { 429 return iHashCode; 430 } 431 432 private class StartSlotsEnum implements IntEnumeration { 433 int day = -1; 434 boolean hasNext = false; 435 436 private StartSlotsEnum() { 437 hasNext = nextDay(); 438 } 439 440 boolean nextDay() { 441 do { 442 day++; 443 if (day >= Constants.DAY_CODES.length) 444 return false; 445 } while ((Constants.DAY_CODES[day] & iDayCode) == 0); 446 return true; 447 } 448 449 @Override 450 public boolean hasMoreElements() { 451 return hasNext; 452 } 453 454 @Override 455 public Integer nextElement() { 456 int slot = (day * Constants.SLOTS_PER_DAY) + iStartSlot; 457 hasNext = nextDay(); 458 return slot; 459 } 460 461 @Deprecated 462 @Override 463 public Integer nextInt() { 464 return nextElement(); 465 } 466 } 467 468 private class DaysEnum extends StartSlotsEnum { 469 private DaysEnum() { 470 super(); 471 } 472 473 @Override 474 public Integer nextElement() { 475 int ret = day; 476 hasNext = nextDay(); 477 return ret; 478 } 479 } 480 481 private class SlotsEnum extends StartSlotsEnum { 482 int pos = 0; 483 484 private SlotsEnum() { 485 super(); 486 } 487 488 private boolean nextSlot() { 489 if (pos + 1 < iLength) { 490 pos++; 491 return true; 492 } 493 if (nextDay()) { 494 pos = 0; 495 return true; 496 } 497 return false; 498 } 499 500 @Override 501 public Integer nextElement() { 502 int slot = (day * Constants.SLOTS_PER_DAY) + iStartSlot + pos; 503 hasNext = nextSlot(); 504 return slot; 505 } 506 } 507 508 @Override 509 public boolean equals(Object o) { 510 if (o == null || !(o instanceof TimeLocation)) 511 return false; 512 TimeLocation t = (TimeLocation) o; 513 if (getStartSlot() != t.getStartSlot()) 514 return false; 515 if (getLength() != t.getLength()) 516 return false; 517 if (getDayCode() != t.getDayCode()) 518 return false; 519 return ToolBox.equals(getTimePatternId(), t.getTimePatternId()) 520 && ToolBox.equals(getDatePatternId(), t.getDatePatternId()); 521 } 522 523 public int getNrWeeks() { 524 return getNrWeeks(0, iWeekCode.size() - 1); 525 } 526 527 public int getNrWeeks(int startDay, int endDay) { 528 /* 529 * BitSet x = new BitSet(1+(endDay-startDay)/Constants.NR_DAYS); for 530 * (int i=iWeekCode.nextSetBit(startDay); i<=endDay && i>=0; 531 * i=iWeekCode.nextSetBit(i+1)) x.set((i-startDay)/Constants.NR_DAYS); 532 * return x.cardinality(); 533 */ 534 int card = iWeekCode.get(startDay, endDay).cardinality(); 535 if (card == 0) 536 return 0; 537 if (card <= 7) 538 return 1; 539 return (5 + card) / 6; 540 } 541 542 public interface IntEnumeration extends Enumeration<Integer> { 543 @Deprecated 544 public Integer nextInt(); 545 } 546 547 private Integer iFirstMeeting = null; 548 public int getFirstMeeting(int dayOfWeekOffset) { 549 if (iFirstMeeting == null) { 550 int idx = -1; 551 while ((idx = getWeekCode().nextSetBit(1 + idx)) >= 0) { 552 int dow = (idx + dayOfWeekOffset) % 7; 553 if ((getDayCode() & Constants.DAY_CODES[dow]) != 0) break; 554 } 555 iFirstMeeting = idx; 556 } 557 return iFirstMeeting; 558 } 559 560 private Integer iLastMeeting = null; 561 public int getLastMeeting(int dayOfWeekOffset) { 562 if (iLastMeeting == null) { 563 int idx = -1; 564 while ((idx = getWeekCode().nextSetBit(1 + idx)) >= 0) { 565 int dow = (idx + dayOfWeekOffset) % 7; 566 if ((getDayCode() & Constants.DAY_CODES[dow]) != 0) 567 iLastMeeting = idx; 568 } 569 } 570 return iLastMeeting; 571 } 572 573 /** List dates when this time location meets. 574 * @return enumeration of dates of this time (indexes to the {@link TimeLocation#getWeekCode()} for matching days of the week) 575 **/ 576 public IntEnumeration getDates(int dayOfWeekOffset) { 577 return new DateEnum(dayOfWeekOffset); 578 } 579 580 /** 581 * Check if the given time location has a particular date 582 * @param date a date, expressed as an index to the {@link TimeLocation#getWeekCode()} 583 * @param dayOfWeekOffset day of the week offset for the weeks pattern 584 * @return true if this time location is meeting on the given date 585 */ 586 public boolean hasDate(int date, int dayOfWeekOffset) { 587 if (date < 0) return false; 588 if (getWeekCode().get(date)) { 589 int dow = (date + dayOfWeekOffset) % 7; 590 if ((getDayCode() & Constants.DAY_CODES[dow]) != 0) return true; 591 } 592 return false; 593 } 594 595 /** 596 * Check if the time location has the given date, identified by a day of the week and a week pattern 597 * @param dayOfWeek day of the week 598 * @param week week pattern, or null when only days of the week are to be checked 599 * @param dayOfWeekOffset day of the week offset for the weeks pattern 600 * @return true if this time location is meeting on the given date 601 */ 602 public boolean hasDate(int dayOfWeek, BitSet week, int dayOfWeekOffset) { 603 // check the day of the week 604 if ((getDayCode() & Constants.DAY_CODES[dayOfWeek]) == 0) return false; 605 if (week == null) { 606 // no week -> just day code check is sufficient 607 return true; 608 } else { 609 // has week -> check the week code 610 // first date in the week 611 int firstDate = week.nextSetBit(0); 612 int dow = (firstDate + dayOfWeekOffset) % 7; // 5 613 // adjustments the given day of the week 614 int adj = (7 - dow + dayOfWeek) % 7; 615 return week.get(firstDate + adj) && getWeekCode().get(firstDate + adj); 616 } 617 } 618 619 /** 620 * Check if the time location has at least one date from a set identified by a day code and a bit set. 621 * Precise computation of individual dates are used instead of just checking whether the day codes and week codes are overlapping. 622 * @param dayCode day codes 623 * @param weekCode week code 624 * @param dayOfWeekOffset day of the week offset for the weeks pattern 625 * @return true if there is at least one overlapping date 626 */ 627 public boolean overlaps(int dayCode, BitSet weekCode, int dayOfWeekOffset) { 628 // check day code 629 if ((getDayCode() & dayCode) == 0) return false; 630 if (weekCode == null) { 631 // no week -> just day code check is sufficient 632 return true; 633 } else { 634 // has week -> check the week code 635 int idx = -1; 636 while ((idx = weekCode.nextSetBit(1 + idx)) >= 0) { 637 // iterate over all dates of the date pattern 638 int dow = (idx + dayOfWeekOffset) % 7; 639 if ((dayCode & Constants.DAY_CODES[dow]) == 0) continue; 640 // check if this date is in the current time location 641 if ((getDayCode() & Constants.DAY_CODES[dow]) != 0 && getWeekCode().get(idx)) 642 return true; 643 } 644 return false; 645 } 646 } 647 648 /** 649 * Count how many times this time location is meeting 650 * @param dayOfWeekOffset day of the week offset for the weeks pattern 651 * @return number of dates during which this time location is meeting 652 */ 653 public int countDates(int dayOfWeekOffset) { 654 int idx = -1; 655 int count = 0; 656 while ((idx = getWeekCode().nextSetBit(1 + idx)) >= 0) { 657 int dow = (idx + dayOfWeekOffset) % 7; 658 if ((getDayCode() & Constants.DAY_CODES[dow]) != 0) count++; 659 } 660 return count; 661 } 662 663 private class DateEnum implements IntEnumeration { 664 int dayOfWeekOffset = 0; 665 int nextDate = -1; 666 boolean hasNext = false; 667 668 private DateEnum(int dayOfWeekOffset) { 669 this.dayOfWeekOffset = dayOfWeekOffset; 670 hasNext = nextDate(); 671 } 672 673 boolean nextDate() { 674 while (true) { 675 nextDate = getWeekCode().nextSetBit(1 + nextDate); 676 if (nextDate < 0) return false; 677 int dow = (nextDate + dayOfWeekOffset) % 7; 678 if ((getDayCode() & Constants.DAY_CODES[dow]) != 0) return true; 679 } 680 } 681 682 @Override 683 public boolean hasMoreElements() { 684 return hasNext; 685 } 686 687 @Override 688 public Integer nextElement() { 689 int ret = nextDate; 690 hasNext = nextDate(); 691 return ret; 692 } 693 694 @Deprecated 695 @Override 696 public Integer nextInt() { 697 return nextElement(); 698 } 699 } 700}