<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;"># a5.py
#  .... SOLUTIONS  ......
# By Will Xiao and Prof. Lee, May 2020

import a3_classes # for the Day and Task class

"""Classes for a more sophisticated todo-list and calendar processing,
   expanding on the class Task from A3."""


# STUDENTS: in the interest of time, you do not need to add assert statements
# to validate preconditions. Helper functions (including methods) are allowed, 
# but must be well-documented with clear specifications.

class SplittableTask(a3_classes.Task):
    """
    An instance represents a task that can be scheduled within a Day.

    SplittableTasks do not have to be scheduled for a single block of consecutive
    hours, but rather can be split up and scheduled in multiple smaller intervals.

    Instance attributes (IN ADDITION TO THOSE IN THE PARENT Task CLASS):
        time_unscheduled: the remaining amount of time left in this task that
                          has yet to be scheduled within a Day 
                          [int &gt;= 0, and no more than this Task's length] 

    Class invariant: 
    the amount of time this SplittableTask has been budgeted for in some 
       presumed calendar of interest, 
    plus this SplittableTask's time_unscheduled,  
    equals this SplittableTask's length.
    """

    def __init__(self, n, time_needed):
        """
        Initializer: a new, completely unscheduled SplittableTask with name `n` 
        and length `time_needed`

        Preconditions:
            `n`: nonempty string
            `time_needed`:  int in 1..24
        """
        super().__init__(n, time_needed)
        self.time_unscheduled = time_needed


    def isAllScheduled(self):
        """
        Returns: True if this SplittableTask is fully scheduled, False otherwise.

        A SplittableTask is fully scheduled if it has 0 unscheduled time left.
        """
        return self.time_unscheduled == 0


    def updateUnsched(self, d, hr):
        """
        Schedules one of this SplittableTask's unscheduled hours for Day `d` at
        time `hr`. Note that `d` thus is altered.
        Preconditions: 
            This SplittableTask has at least one unscheduled hour left.
            `hr` is a legitimate time for day `d`.
            `d` has hour `hr` free.
        """
        # check if this SplittableTask is already in d.time_slots
        if self not in d.time_slots:
            # This is a new Task for this Day.
            d.num_tasks_scheduled += 1

        d.time_slots[hr] = self
        self.time_unscheduled -= 1


    def scheduleSome(self, day):
        """
        Returns: True if a non-zero amount of the unscheduled time in this
          SplittableTask can be scheduled into `day`; 
          and if so, this method does the scheduling of as much of the 
          unscheduled time into `day` as possible, using the earliest empty
          time slots.
        Returns: True *also* in the case that this SplittableTask already has 
          no more unscheduled time left.
        Returns False otherwise (i.e., there's still more of this SplittableTask
        to do but `day` is already full), with no alteration of `day`.

        Parameter `day`: the Day to try to schedule some of this SplittableTask
           into. 
        Precondition: `day` is a Day object (not None)
        """

        if  not self.isAllScheduled() and None not in day.time_slots:
            return False

        # If we get here, we know at least one empty time slot exists
   
        i = 0
        while (i &lt; len(day.time_slots) and not self.isAllScheduled()):
            if day.time_slots[i] is None:
                self.updateUnsched(day, i)
            i += 1
                
        return True  

        # ALTERNATE VERSION (there are many possibilities)
        # Prof. Lee's solution to the last while-loop
        
        search_start = 0 # place where next None could be, or &gt;= len(day.time_slots)
        while None in day.time_slots[search_start:] and not self.isAllScheduled():
            nonePos = day.time_slots.index(None, search_start)
            # print("DEBUG: nonePos: " + str(nonePos))
            self.updateUnsched(day, nonePos)             
            search_start = nonePos + 1
        return True

    
    # STUDENTS: we've done the __str__ implementation for you, to save you time
    # and potentially help you debug other methods.
    def __str__(self):
        """
        Returns: A string representation of this SplittableTask.

        The string is formatted as:
            "SplittableTask with name: &lt;name&gt;, length: &lt;length&gt;, and time unscheduled &lt;time_unscheduled&gt;"
        
        where &lt;name&gt; is the name attribute, 
        &lt;length&gt; is the length attribute, 
        and &lt;time_unscheduled&gt; is the time_unscheduled attribute. 
        Example: if we executed 
            sleep = SplittableTask("nap", 8)
        then the result of __str__ at that point should be the string
            'SplittableTask with name: nap, length: 8, and time unscheduled 8'
        """
        return ("SplittableTask with name: " + self.name + 
                                ", length: " + str(self.length) + 
                                ", and time unscheduled " + str(self.time_unscheduled))



class Month():
    """
    An instance represents one of January, February, March, ..., December.

    Instance attributes:
        month_num: The number of this month [int in 1..12]
        day_list: The days in this month [list of a3_classes.Day objects]
            Constraints on day_list:
            - The number of Day objects in day_list equals the number of days
              in the month corresponding to this Month. 
              Note: in the Land of A5, February always has exactly 28 days.
            - For each Day in day_list, the Day object's name has format 
              "&lt;month number&gt;/&lt;day number&gt;".
              Example: if this Month's month_num is 5, the item at index 0 in
                `day_list` should have name "5/1", for the 1st day of May.
            - The Day objects are in ascending order by day number as 
              listed in the Day's names; e.g., the Day with name "5/9" 
              immediately the precedes the Day with name "5/10".             
    """

    def __init__(self, month):
        """
        Initializer: Creates a Month with the month-number attribute `month_num`
        set to the value of `month` and the attribute `day_list` set as 
        specified in the invariant for class Month.

        Precondition: `month` is an int in 1..12.
        """

        assert type(month) == int and 1 &lt;= month and month &lt;= 12, \
            "Month is invalid: " + repr(month)
        self.month_num = month
        self.day_list = []
        i = 1
        while i &lt;= _daysInMonth(month):
            date = a3_classes.Day(str(month) + "/" + str(i))
            self.day_list.append(date)
            i += 1

        return # To prevent alternate solution below from also running

        # Alternate solution for the while-loop
        while len(self.day_list) &lt; _daysInMonth(month):
            next_num = len(self.day_list)+1
            date = a3_classes.Day(str(month) + "/" + str(next_num))
            self.day_list.append(date)


# Helper function
def _daysInMonth(month_number):
    """
    Returns: the number of days in the month with number month_number.

    Example: daysInMonth(1) returns 31, because there are 31 days in January.
    """
    # For the sake of this assignment, February always has 28 days.
    # Sorry, calendar enthusiasts.
    if month_number == 2: # February
        return 28
    if month_number in [4, 6, 9, 11]: # the months with 30 days
        return 30
    # Any month that makes it to this point has 31 days in it
    return 31


# Some quick testing code

def print_day(d):
    for i in range(len(d.time_slots)):
        print(i, d.time_slots[i])

if __name__ == '__main__':
    import testcase
    t = SplittableTask("test", 13)
    # check methods all exist
    testcase.assert_equals(False, t.isAllScheduled())
    tfull = SplittableTask("allfull", 13)
    tfull.time_unscheduled = 0
    testcase.assert_equals(0, tfull.time_unscheduled)
    # print(tfull)

    t1 = SplittableTask("task 12 hours", 12)
    t2 = SplittableTask("task 14 hours", 14)

    d = a3_classes.Day("Monday")
    d.time_slots[3] = a3_classes.Task("blocked", 1)

    for newtask in [t1, t2]:
        newtask.scheduleSome(d)
        print("checking if the task " + newtask.name + " was scheduled in the day")
        print_day(d)
        print("number of tasks in this day: " + str(d.num_tasks_scheduled))
        print("details now for task: " + str(newtask))

    import a5_tests
    a5_tests.run_tests()

</pre></body></html>