/// <summary> /// How many credit points worth of subjects still need to be taken before this plan is valid? /// </summary> public int RemainingCreditPoints() { if (!SelectedCourses.Any()) { return(int.MaxValue); } return(SelectedCourses.First().CreditPoints() - SelectedSubjects.Sum(subject => subject.CreditPoints())); }
/// <summary> /// Remove content from the study plan /// </summary> /// <param name="content">A subject or course that is being removed from the plan</param> public void RemoveContent(Content content) { if (content is Subject) { SelectedSubjects.Remove(content as Subject); } else { SelectedCourses.Remove(content as Course); } RefreshBannedSubjectsList(); RefreshRelations(); Order(); }
public void RegisterTalks() { try { var newSubjects = SubjectsLoader.Load(); //if (CannotBeRegistered(newSubjects)) // advance code to check the limits // throw new ArgumentException("Exceeding Time Limit"); SelectedSubjects.InsertRange(SelectedSubjects.Count, newSubjects); } catch (ArgumentException e) { throw; } }
/// <summary> /// Add content(s) to this study plan /// </summary> /// <param name="contents">A list of subjects or courses that need to be added</param> public void AddContents(IEnumerable <Content> contents) { contents = contents.Except(SelectedSubjects); if (!contents.Any()) { return; } foreach (Content content in contents) { if (content is Subject) { SelectedSubjects.Add(content as Subject); } else { SelectedCourses.Add(content as Course); } } RefreshBannedSubjectsList(); RefreshRelations(); Order(); }
/// <summary> /// Find out the relation between all selected subjects to see if any are required /// </summary> private void RefreshRelations() { ContentRelations.Clear(); // Iterate over every selected subject to see what it links to foreach (Content content in SelectedSubjects.Cast <Content>().Concat(SelectedCourses)) { // Use a breadth-first search for subjects that this subject rely on Queue <(Decision, Edge.Importance)> toAnalyze = new Queue <(Decision, Edge.Importance)>(); toAnalyze.Enqueue((content.Prerequisites, Edge.Importance.Compulsory)); toAnalyze.Enqueue((content.Corequisites, Edge.Importance.Compulsory)); while (toAnalyze.Any()) { (Decision requisite, Edge.Importance importance) = toAnalyze.Dequeue(); // Ignore electives if (requisite.IsElective()) { continue; } // Rank the importance according to whether the option is compulsory or not importance = (importance == Edge.Importance.Compulsory && requisite.MustPickAll()) ? Edge.Importance.Compulsory : Edge.Importance.Optional; foreach (Option option in requisite.Options) { // Search the sub-decisions if (option is Decision decision) { toAnalyze.Enqueue((decision, importance)); } // If the option has been selected, create an edge from the subject to this option else if (SelectedSubjects.Contains(option)) { ContentRelations.Add(new Edge { source = content, importance = importance, dest = option as Content }); } } } } }
public Subject GetSubjectByName(string topic) { return(SelectedSubjects.FirstOrDefault(talk => string.Equals(talk.Topic, topic, StringComparison.OrdinalIgnoreCase))); }
/// <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); }