    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}