    001package org.cpsolver.instructor.model;
    002
    003import java.util.Collection;
    004
    005import org.cpsolver.coursett.Constants;
    006import org.cpsolver.coursett.model.TimeLocation;
    007import org.cpsolver.instructor.criteria.DifferentLecture;
    008
    009/**
    010 * Section. A section (part of a teaching request that needs an instructor) has an id, a name, a time, a room.
    011 * A section may be allowed to overlap in time (in which case the overlapping time is to be minimized) and/or
    012 * marked as common. This is, for instance, to be able to ensure that all assignments of a course that are
    013 * given to a single instructor share the same lecture.
    014 *
    015 * @author  Tomáš Müller
    016 * @version IFS 1.3 (Instructor Sectioning)<br>
    017 *          Copyright (C) 2016 Tomáš Müller<br>
    018 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    019 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    020 * <br>
    021 *          This library is free software; you can redistribute it and/or modify
    022 *          it under the terms of the GNU Lesser General Public License as
    023 *          published by the Free Software Foundation; either version 3 of the
    024 *          License, or (at your option) any later version. <br>
    025 * <br>
    026 *          This library is distributed in the hope that it will be useful, but
    027 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    028 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    029 *          Lesser General Public License for more details. <br>
    030 * <br>
    031 *          You should have received a copy of the GNU Lesser General Public
    032 *          License along with this library; if not see
    033 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    034 */
    035public class Section {
    036    private Long iId;
    037    private String iExternalId;
    038    private String iType;
    039    private String iName;
    040    private TimeLocation iTime;
    041    private String iRoom;
    042    private boolean iAllowOverlap;
    043    private boolean iCommon;
    044
    045    /**
    046     * Constructor
    047     * @param id section unique id
    048     * @param externalId section external id
    049     * @param type instructional type
    050     * @param name section name
    051     * @param time section time assignment
    052     * @param room section room assignment
    053     * @param allowOverlap can this section overlap with some other section
    054     * @param common is this a common part of the course
    055     */
    056    public Section(long id, String externalId, String type, String name, TimeLocation time, String room, boolean allowOverlap, boolean common) {
    057        iId = id;
    058        iExternalId = externalId;
    059        iType = type;
    060        iName = name;
    061        iTime = time;
    062        iRoom = room;
    063        iAllowOverlap = allowOverlap;
    064        iCommon = common;
    065    }
    066
    067    /**
    068     * Section unique id that was provided in the constructor
    069     * @return section unique id
    070     */
    071    public Long getSectionId() { return iId; }
    072
    073    /**
    074     * Section external unique id that was provided in the constructor
    075     * @return section external id
    076     */
    077    public String getExternalId() { return iExternalId; }
    078
    079    /**
    080     * Section instructional type (e.g., Lecture) that was provided in the constructor
    081     * @return section instructional type
    082     */
    083    public String getSectionType() { return iType; }
    084
    085    /**
    086     * Has section type filled in?
    087     * @return true, if there is a section instructional type filled in
    088     */
    089    public boolean hasSectionType() { return iType != null && !iType.isEmpty(); }
    090
    091    /**
    092     * Section name that was provided in the constructor
    093     * @return section name
    094     */
    095    public String getSectionName() { return iName; }
    096
    097    /**
    098     * Section time that was provided in the constructor
    099     * @return section time
    100     */
    101    public TimeLocation getTime() { return iTime; }
    102
    103    /**
    104     * Has section time filled in?
    105     * @return true if section is assigned in time
    106     */
    107    public boolean hasTime() { return getTime() != null && getTime().getDayCode() != 0; }
    108
    109    /**
    110     * Section room (or rooms)
    111     * @return section room
    112     */
    113    public String getRoom() { return iRoom; }
    114
    115    /**
    116     * Has section room filled in?
    117     * @return true if section is assigned in space
    118     */
    119    public boolean hasRoom() { return iRoom != null && !iRoom.isEmpty(); }
    120
    121    /**
    122     * Are time overlaps with other sections and with prohibited time preferences allowed? If true, the number of overlapping time slots should be minimized instead.
    123     * @return true if other sections can overlap with this section or if the student can teach this section even when he/she is unavailable
    124     */
    125    public boolean isAllowOverlap() { return iAllowOverlap; }
    126
    127    /**
    128     * Is common part of the course (e.g., a lecture)? It is possible to either require all assignments of a course that are given to the same instructor to have
    129     * to share the common part (when {@link TeachingRequest#getSameCommonPreference()} is true) or to minimize the different sections that are not shared among all the assignments
    130     * (using {@link DifferentLecture} criterion and {@link TeachingRequest#nrSameLectures(TeachingRequest)}).
    131     * @return true if this section forms a common part of the course
    132     */
    133    public boolean isCommon() { return iCommon; }
    134
    135    /**
    136     * Check if this section overlaps in time with some other section
    137     * @param section the other section
    138     * @return true, if neither of the two sections allow for overlap and they are assigned in overlapping times (see {@link TimeLocation#hasIntersection(TimeLocation)})
    139     */
    140    public boolean isOverlapping(Section section) {
    141        if (isAllowOverlap() || section.isAllowOverlap()) return false;
    142        if (getTime() == null || section.getTime() == null) return false;
    143        return getTime().hasIntersection(section.getTime());
    144    }
    145
    146    /**
    147     * Check if this section overlaps in time with at least one of the given sections
    148     * @param sections the other sections
    149     * @return true, if there is a section among the sections that overlaps in time with this section (see {@link Section#isOverlapping(Section)})
    150     */
    151    public boolean isOverlapping(Collection<Section> sections) {
    152        if (isAllowOverlap()) return false;
    153        if (getTime() == null) return false;
    154        if (sections.contains(this)) return false;
    155        for (Section section : sections) {
    156            if (section.isAllowOverlap()) continue;
    157            if (section.getTime() == null) continue;
    158            if (getTime().hasIntersection(section.getTime())) return true;
    159        }
    160        return false;
    161    }
    162
    163    /**
    164     * Check if this section is back to back with some other section
    165     * @param section the other section
    166     * @return true, if this section is back-to-back with the other section
    167     */
    168    public boolean isBackToBack(Section section) {
    169        if (getTime() == null || section.getTime() == null) return false;
    170        return getTime().shareWeeks(section.getTime()) && getTime().shareDays(section.getTime()) &&
    171                (getTime().getStartSlot() + getTime().getLength() == section.getTime().getStartSlot() || section.getTime().getStartSlot() + section.getTime().getLength() == getTime().getStartSlot());
    172    }
    173
    174    /**
    175     * Check if this section has the same days as some other section
    176     * @param section the other section
    177     * @return 0 if all the days are different, 1 if all the days are the same
    178     */
    179    public double percSameDays(Section section) {
    180        if (getTime() == null || section.getTime() == null) return 0;
    181        double ret = 0.0;
    182        for (int dayCode: Constants.DAY_CODES)
    183            if ((getTime().getDayCode() & dayCode) != 0 && (section.getTime().getDayCode() & dayCode) != 0) ret ++;
    184        return ret / Math.min(getTime().getNrMeetings(), section.getTime().getNrMeetings());
    185    }
    186
    187    /**
    188     * Check if this section is placed in the same room as the other section
    189     * @param section the other section
    190     * @return true, if both sections have no room or if they have the same room
    191     */
    192    public boolean isSameRoom(Section section) {
    193        return hasRoom() == section.hasRoom() && (!hasRoom() || getRoom().equals(section.getRoom()));
    194    }
    195
    196    /**
    197     * Check if this section has the same instructional type as the other section
    198     * @param section the other section
    199     * @return true, if both sections have no instructional type or if they have the same instructional type
    200     */
    201    public boolean isSameSectionType(Section section) {
    202        return hasSectionType() == section.hasSectionType() && (!hasSectionType() || getSectionType().equals(section.getSectionType()));
    203    }
    204
    205    /**
    206     * Check if this section is back-to-back with some other section in the list
    207     * @param sections the other sections
    208     * @param diffRoomWeight different room penalty (should be between 1 and 0)
    209     * @param diffTypeWeight different instructional type penalty (should be between 1 and 0)
    210     * @return 1.0 if there is a section in the list that is back-to-back and in the same room and with the same type, 0.0 if there is no back-to-back section in the list, etc.
    211     * If there are multiple back-to-back sections, the best back-to-back value is returned.
    212     */
    213    public double countBackToBacks(Collection<Section> sections, double diffRoomWeight, double diffTypeWeight) {
    214        if (sections.contains(this)) return 0.0;
    215        double btb = 0;
    216        for (Section section : sections)
    217            if (isBackToBack(section)) {
    218                double w = 1.0;
    219                if (!isSameRoom(section)) w *= diffRoomWeight;
    220                if (!isSameSectionType(section)) w *= diffTypeWeight;
    221                if (w > btb) btb = w;
    222            }
    223        return btb;
    224    }
    225
    226    /**
    227     * Check if this section has the same days with some other section in the list
    228     * @param sections the other sections
    229     * @param diffRoomWeight different room penalty (should be between 1 and 0)
    230     * @param diffTypeWeight different instructional type penalty (should be between 1 and 0)
    231     * @return 1.0 if there is a section in the list that has the same days and in the same room and with the same type, 0.0 if there is no same days section in the list, etc.
    232     * If there are multiple same days sections, the best same days value is returned.
    233     */
    234    public double countSameDays(Collection<Section> sections, double diffRoomWeight, double diffTypeWeight) {
    235        if (sections.contains(this)) return 0.0;
    236        double sd = 0;
    237        for (Section section : sections) {
    238            double w = percSameDays(section);
    239            if (!isSameRoom(section)) w *= diffRoomWeight;
    240            if (!isSameSectionType(section)) w *= diffTypeWeight;
    241            if (w > sd) sd = w;
    242        }
    243        return sd;
    244    }
    245
    246    /**
    247     * Check if this section has the same room with some other section in the list
    248     * @param sections the other sections
    249     * @param diffTypeWeight different instructional type penalty (should be between 1 and 0)
    250     * @return 1.0 if there is a section in the list that has the same room and with the same type, 0.0 if there is no same room section in the list, etc.
    251     * If there are multiple same room sections, the best same room value is returned.
    252     */
    253    public double countSameRooms(Collection<Section> sections, double diffTypeWeight) {
    254        if (sections.contains(this)) return 0.0;
    255        double sr = 0;
    256        for (Section section : sections) {
    257            if (isSameRoom(section)) {
    258                double w = 1.0;
    259                if (!isSameSectionType(section)) w *= diffTypeWeight;
    260                if (w > sr) sr = w;
    261            }
    262        }
    263        return sr;
    264    }
    265
    266    /**
    267     * If this section can overlap in time with the other section, compute the number of overlapping time slots
    268     * @param section the other section
    269     * @return number of shared days times number of shared slots (a day)
    270     */
    271    public int share(Section section) {
    272        if (getTime() != null && section.getTime() != null && (isAllowOverlap() || section.isAllowOverlap()) && getTime().hasIntersection(section.getTime()))
    273            return getTime().nrSharedDays(section.getTime()) * getTime().nrSharedHours(section.getTime());
    274        else
    275            return 0;
    276    }
    277
    278    /**
    279     * Compute the number of overlapping time slots between this section and the given time
    280     * @param time the given time
    281     * @return number of shared days times number of shared slots (a day)
    282     */
    283    public int share(TimeLocation time) {
    284        if (getTime() != null && time != null && getTime().hasIntersection(time))
    285            return getTime().nrSharedDays(time) * getTime().nrSharedHours(time);
    286        else
    287            return 0;
    288    }
    289
    290    /**
    291     * If this section can overlap in time with any of the given section, compute the number of overlapping time slots
    292     * @param sections the other sections
    293     * @return number of shared days times number of shared slots (a day)
    294     */
    295    public int share(Collection<Section> sections) {
    296        if (sections.contains(this)) return 0;
    297        int ret = 0;
    298        for (Section section : sections)
    299            ret += share(section);
    300        return ret;
    301    }
    302
    303    /**
    304     * Format the time statement
    305     * @param useAmPm use 12-hours or 24-hours format
    306     * @return &lt;Days of week&gt; &lt;Start time&gt; - &lt;End time&gt;
    307     */
    308    public String getTimeName(boolean useAmPm) {
    309        if (getTime() == null || getTime().getDayCode() == 0) return "-";
    310        return getTime().getDayHeader() + " " + getTime().getStartTimeHeader(useAmPm) + " - " + getTime().getEndTimeHeader(useAmPm);
    311    }
    312
    313    @Override
    314    public String toString() {
    315        return (getExternalId() != null ? (getSectionType() == null ? "" : getSectionType() + " ") + getExternalId() :
    316            getSectionName() != null ? getSectionName() : getTime() != null ? getTime().getName(true) : "S" + getSectionId());
    317    }
    318
    319    @Override
    320    public int hashCode() {
    321        return getSectionId().hashCode();
    322    }
    323
    324    @Override
    325    public boolean equals(Object o) {
    326        if (o == null || !(o instanceof Section)) return false;
    327        Section s = (Section)o;
    328        return getSectionId().equals(s.getSectionId());
    329    }
    330}