@@ -184,6 +184,104 @@ function timeStringToMinutes($str) {
184184 return $ hour * 60 + $ minute ;
185185}
186186
187+ /**
188+ * Checks if a section is a special section (lab/studio/etc)
189+ * @param $courseInfo
190+ * @return int
191+ */
192+ function isSpecialSection ($ courseInfo ) {
193+ return preg_match ('/[A-Z]\d{0,2}$/ ' , $ courseInfo ['courseNum ' ]) === 1 ;
194+ }
195+
196+ /**
197+ * Returns a cleaned course number, free of special sections or designators
198+ * @param $courseInfo
199+ * @return mixed
200+ */
201+ function getCleanCourseNum ($ courseInfo ) {
202+ $ matches = array ();
203+ if (preg_match ('/^(.*?)-?(?:[A-Z]\d{0,2})$/ ' , $ courseInfo ['courseNum ' ], $ matches ) === 1 ) {
204+ return $ matches [1 ];
205+ } else {
206+ return $ courseInfo ['courseNum ' ];
207+ }
208+ }
209+
210+ /**
211+ * Prunes invalid schedules based on courseGroups
212+ * @param $schedules
213+ * @param $courseGroups
214+ * @return array
215+ */
216+ function pruneSpecialCourses ($ schedules , $ courseGroups ) {
217+
218+ // The array of schedules that meet all course requirements
219+ $ validSchedules = array ();
220+
221+ // Loop through each possible schedule
222+ foreach ($ schedules as $ schedule ) {
223+
224+ // Flattened schedule [courseNum => <value>] where <value> is:
225+ // false: no co-requirements
226+ // true: is a co-requirement
227+ // string[]: list of possible requirements
228+ $ flattenedSchedule = array ();
229+
230+ // Loop through each course
231+ foreach ($ schedule as $ course ) {
232+
233+ $ cleanCourseNum = getCleanCourseNum ($ course );
234+
235+ // This course has selected labs or is a lab
236+ if (array_key_exists ($ cleanCourseNum , $ courseGroups ) && count ($ courseGroups [$ cleanCourseNum ]) > 0 ) {
237+ if (!isSpecialSection ($ course )) {
238+
239+ // Set the course requirement as an array of courseNum strings
240+ $ flattenedSchedule [$ course ['courseNum ' ]] = array_keys ($ courseGroups [$ cleanCourseNum ]);
241+ } else {
242+ $ flattenedSchedule [$ course ['courseNum ' ]] = true ;
243+ }
244+ } else {
245+ $ flattenedSchedule [$ course ['courseNum ' ]] = false ;
246+ }
247+ }
248+
249+ $ scheduleMeetsRequirements = true ;
250+ // Loop through the flatten schedules
251+ foreach ($ flattenedSchedule as $ courseNum => $ courseRequirements ) {
252+
253+ // Check if course has requirements
254+ if (is_array ($ courseRequirements )) {
255+ $ courseMeetsRequirement = false ;
256+
257+ // Loop through the requirements, checking if the schedule contains AT LEAST one required course
258+ foreach ($ courseRequirements as $ specialCourseNum ) {
259+ if (array_key_exists ($ specialCourseNum , $ flattenedSchedule )) {
260+ $ courseMeetsRequirement = true ;
261+ break ;
262+ }
263+ }
264+
265+ // "AND" the previous results with the current one
266+ $ scheduleMeetsRequirements = $ scheduleMeetsRequirements && $ courseMeetsRequirement ;
267+
268+ // Don't bother checking other courses if the schedule already does not meet requirements
269+ if (!$ scheduleMeetsRequirements ) {
270+ continue ;
271+ }
272+ }
273+ }
274+
275+ // Add this to the valid schedules if it meets all requirements
276+ if ($ scheduleMeetsRequirements ) {
277+ $ validSchedules [] = $ schedule ;
278+ }
279+ }
280+
281+ // Return the resulting array of all schedules that met co-requirements
282+ return $ validSchedules ;
283+ }
284+
187285////////////////////////////////////////////////////////////////////////////
188286// MAIN EXECUTION
189287
@@ -291,20 +389,93 @@ function timeStringToMinutes($str) {
291389 // GET MATCHING SCHEDULES
292390 case "getMatchingSchedules " :
293391 // Process the list of courses that were selected
392+
393+ // Keep track of grouped classes by both clean course name (sections) and by course input index
394+ /**
395+ * array(string {cleanCourseNum} => array(string {courseNum} => {courseInfo array})))
396+ */
397+ $ courseGroups = array ();
398+
399+ /**
400+ * array(int {course input index} => array(integer {courseId} => array(string {courseNum} => {courseInfo array})))
401+ */
402+ $ courseGroupsByCourseId = array ();
403+
294404 $ courseSet = array ();
295405 for ($ i = 1 ; $ i <= $ _POST ['courseCount ' ]; $ i ++) { // It's 1-indexed... :[
296406 // Iterate over the courses in that course slot
297407 if (!isset ($ _POST ["courses {$ i }Opt " ])) { continue ; }
298408 $ courseSubSet = array ();
409+ $ courseGroupsByCourseId [$ i ] = array ();
299410 foreach ($ _POST ["courses {$ i }Opt " ] as $ course ) {
411+
300412 // Do a query to get the course specified
301- $ courseInfo = getCourseBySectionId ($ course );
302- $ courseInfo ['courseIndex ' ] = $ i ;
303- $ courseSubSet [] = $ courseInfo ;
413+ $ courseInfo = getCourseBySectionId ($ course );
414+
415+ // courseIndex is only used by the frontend UI to determine what color/grouping to use
416+ $ courseInfo ['courseIndex ' ] = $ i ;
417+
418+ // Remove the potential special indicators from the end of the courseNum
419+ $ cleanCourseNum = getCleanCourseNum ($ courseInfo );
420+
421+ // Create the group if it does not already exist
422+ if (!array_key_exists ($ cleanCourseNum , $ courseGroups )) {
423+ $ courseGroups [$ cleanCourseNum ] = array ();
424+ }
425+
426+ // Create the group by index and course id. Can probably ignore courseId, but will be eventually useful
427+ if (!array_key_exists ($ courseInfo ['courseId ' ], $ courseGroupsByCourseId [$ i ])) {
428+ $ courseGroupsByCourseId [$ i ][$ courseInfo ['courseId ' ]] = array ();
429+ }
430+
431+ // Check if the section is a special course: courseNum ending in a letter, then one or two digits
432+ if (isSpecialSection ($ courseInfo )) {
433+
434+ if (!array_key_exists ($ courseInfo ['courseNum ' ], $ courseGroups [$ cleanCourseNum ])) {
435+
436+ // Add this course to its group
437+ $ courseGroups [$ cleanCourseNum ][$ courseInfo ['courseNum ' ]] = $ courseInfo ;
438+ }
439+
440+ if (!array_key_exists ($ courseInfo ['courseNum ' ], $ courseGroupsByCourseId [$ i ][$ courseInfo ['courseId ' ]])) {
441+
442+ // Add this course to its group by course id
443+ $ courseGroupsByCourseId [$ i ][$ courseInfo ['courseId ' ]][$ courseInfo ['courseNum ' ]] = $ courseInfo ;
444+ }
445+
446+ } else {
447+
448+ // This is a normal class, it can be added like normal to the sub set
449+ $ courseSubSet [] = $ courseInfo ;
450+ }
451+ }
452+
453+ // Add the normal subset to the main set
454+ if (count ($ courseSubSet ) > 0 ) {
455+ $ courseSet [] = $ courseSubSet ;
304456 }
305- $ courseSet [] = $ courseSubSet ;
306457 }
307-
458+
459+
460+ // Loop through each course groups' courses and flatten the array
461+ if (count ($ courseGroups ) > 0 ) {
462+ foreach ($ courseGroupsByCourseId as $ courseGroupsByIndex ) {
463+ $ specialCourseSubSet = array ();
464+ foreach ($ courseGroupsByIndex as $ courseGroup ) {
465+ // Get each special course
466+ foreach ($ courseGroup as $ specialCourse ) {
467+ $ specialCourseSubSet [] = $ specialCourse ;
468+ }
469+ }
470+
471+ // Add any special courses for this index to the main courseSet.
472+ if (count ($ specialCourseSubSet ) > 0 ) {
473+ $ courseSet [] = $ specialCourseSubSet ;
474+ }
475+ }
476+ }
477+
478+ // Set the courseIndex for the remaining nonCourse/noCourse routines
308479 $ courseIndex = $ i ;
309480
310481 // Process the list of nonCourse Items
@@ -358,7 +529,7 @@ function timeStringToMinutes($str) {
358529 // Generate valid schedules, and include the errors if we're being verbose
359530 $ results = array ();
360531 if (!empty ($ courseSet )) {
361- $ results ['schedules ' ] = generateSchedules ($ courseSet , $ nonCourseSet , $ noCourseSet );
532+ $ results ['schedules ' ] = pruneSpecialCourses ( generateSchedules ($ courseSet , $ nonCourseSet , $ noCourseSet), $ courseGroups );
362533 } else {
363534 $ results ['schedules ' ] = array (array ());
364535 }
0 commit comments