Пример #1
0
 /// <summary>
 /// Remove the record that says a subject needs to run on a specific time
 /// </summary>
 public void UnForceTime(Subject subject)
 {
     if (SubjectsWithForcedTimes.Remove(subject))
     {
         Order();
     }
 }
Пример #2
0
 /// <summary>
 /// Find what time a subject is forced to
 /// </summary>
 /// <returns>If the subject is not forced on any time, return null</returns>
 public Time?GetForcedTime(Subject subject)
 {
     if (SubjectsWithForcedTimes.Contains(subject))
     {
         return(AssignedTimes[subject]);
     }
     return(null);
 }
Пример #3
0
        /// <summary>
        /// Assign a time to all selected subjects
        /// </summary>
        private void Order()
        {
            Stopwatch timerOrder = new Stopwatch();

            timerOrder.Restart();

            // Check that all forcedTimes are allowed
            foreach (Subject subject in SubjectsWithForcedTimes)
            {
                if (!subject.AllowedDuringSemester(AssignedTimes[subject], this))
                {
                    SubjectsWithForcedTimes.Remove(subject);
                }
            }

            // Remove current time assignments
            foreach (Subject subject in SelectedSubjects)
            {
                if (!SubjectsWithForcedTimes.Contains(subject))
                {
                    AssignedTimes.Remove(subject);
                }
            }

            // This variable works because IEnumerables get evaluated every time a method is called on it. I usually use a local function instead of a variable.
            IEnumerable <Subject> RemainingSubjects = SelectedSubjects.Except(SelectedSubjectsSoFar(Time.All)).OrderBy(subject => subject.GetLevel());

            for (Time semester = Time.First; MaxCreditPoints.Keys.Contains(semester) || RemainingSubjects.Any(); semester = semester.Next())
            {
                // If neccessary, add another year to the planner
                if (!MaxCreditPoints.Keys.Contains(semester))
                {
                    AddYear();
                }
                // Fill the semester with subjects that can be chosen
                int selectedCredits = SelectedSubjects.Where(subject => AssignedTimes.TryGetValue(subject, out Time selectedTime) && selectedTime == semester).Sum(subject => subject.CreditPoints());
                while (selectedCredits < GetMaxCreditPoints(semester))
                {
                    // Prepare a list of what subjects could be chosen
                    var possibleSubjects = RemainingSubjects;
                    // Do not pick subjects with forced times later than the current session
                    possibleSubjects = possibleSubjects.Where(subject => !(SubjectsWithForcedTimes.Contains(subject) && semester.IsEarlierThan(AssignedTimes[subject])));
                    // Pick from subjects that are allowed during this semester
                    possibleSubjects = possibleSubjects.Where(subject => subject.AllowedDuringSemester(semester, this));
                    // Pick from subjects which would not result in Credit Overflow
                    possibleSubjects = possibleSubjects.Where(subject => subject.CreditPoints() + selectedCredits <= GetMaxCreditPoints(semester));
                    // Check if any subjects are forced
                    IEnumerable <Subject> forcedSubjects = possibleSubjects
                                                           .Where(subject => SubjectsWithForcedTimes.Contains(subject) && AssignedTimes[subject].IsEarlierThanOrAtTheSameTime(semester));
                    // If any subjects are forced, only consider the forced subjects
                    if (forcedSubjects.Any())
                    {
                        possibleSubjects = forcedSubjects.OrderBy(subject => AssignedTimes[subject]);
                    }
                    // Otherwise, filter subjects according to whether their requisites are completed
                    else
                    {
                        possibleSubjects = possibleSubjects.Where(subject => RequisitesHaveBeenSelected(subject, semester));
                    }
                    // Favor subjects that have many other subjects relying on them
                    possibleSubjects = possibleSubjects.OrderByDescending(subject => RemainingSubjects.Sum(other => IsAbove(parent: other, child: subject, out int size) ? size : 0))
                                       // Favor subjects that cannot fit in many semesters
                                       .ThenBy(subject => subject.Semesters.Select(semester => semester.session).Distinct().Count())
                                       // Favor lower level subjects
                                       .ThenBy(subject => subject.GetLevel())
                                       // Favor subjects that don't require many electives as prerequisites
                                       .ThenBy(subject => subject.Prerequisites.SizeOfElective() + subject.Corequisites.SizeOfElective())
                                       // Favor subjects that are requisites to their Degree (or any course)
                                       .ThenBy(subject => ContentRelations.Any(relation => relation.source is Course && relation.dest == subject) ? 0 : 1);
                    // Pick the first item from that list
                    Subject nextSubject = possibleSubjects.FirstOrDefault();
                    // If no subject was chosen, go to the next semester
                    if (nextSubject == null)
                    {
                        break;
                    }
                    // Add the selected subject to this semester
                    AssignedTimes[nextSubject] = semester;
                    // Keep track of how many more times this loop can repeat
                    selectedCredits += nextSubject.CreditPoints();
                }
            }

            if (SelectedSubjects.Except(SelectedSubjectsSoFar(Time.All)).Any())
            {
                throw new InvalidOperationException("Not all the subjects were added to the table");
            }

            timerOrder.Stop();
            Debug.WriteLine("Ordering Plan:       " + timerOrder.ElapsedMilliseconds + "ms");

            // Helper functions to determine whether a subject can be picked

            bool RequisitesHaveBeenSelected(Subject subject, Time time, Decision requisite = null)
            {
                // This function goes through a subject's requisites to make sure every SelectedSubject that is a requisite to this subject has already been added to the schedule

                // If no requisite has been specified, then this must have been called by outside this function
                if (requisite == null)
                {
                    // Check if this is one of those subjects which don't have any actual requisites (eg no data or "permission by special approval")
                    // If so, assign it an earliest position based on its level
                    if (subject.Prerequisites.HasBeenCompleted() && subject.Corequisites.HasBeenCompleted())
                    {
                        return(time.AsNumber() / 2 >= subject.GetLevel() - 1);
                    }
                    // Call this function again but with the prerequisites and corequisites
                    return(RequisitesHaveBeenSelected(subject, time.Previous(), subject.Prerequisites) && RequisitesHaveBeenSelected(subject, time, subject.Corequisites));
                }

                // If the requisit is met by the scheduled selected subjects, return true
                if (requisite.HasBeenCompleted(this, time))
                {
                    return(true);
                }
                // If the requisit is an elective, don't process it. Instead, compare the subject's level to the time
                if (requisite.IsElective())
                {
                    return(subject.GetLevel() <= time.Next().year);
                }
                // Iterate over all of the requisite's option
                // The requisite's selectionType doesn't matter, all that matters is whether it HasBeenCompleted.
                foreach (Option option in requisite.Options)
                {
                    // If the option is a subject that needs to be picked, hasn't been picked, and must come before the current subject: return false
                    if (option is Content content && !SelectedSubjectsSoFar(time).Contains(content) && OptionMustComeBeforeSubject(parent: subject, child: content as Subject))
                    {
                        return(false);
                    }
Пример #4
0
 /// <summary>
 /// Record that a subject needs to run on a specific time
 /// </summary>
 public void ForceTime(Subject subject, Time time)
 {
     AssignedTimes[subject] = time;
     SubjectsWithForcedTimes.Add(subject);
     Order();
 }