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