public static function compute_groups($input_data) { // Step 1: Set up empty groups & alternative format of input. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $Num_Stu = count($input_data); //$Num_Groups = ($Num_Stu - $Num_Stu % 5) / 5; // The documented algorithm. $Num_Groups = max(1, ($Num_Stu - $Num_Stu % 4) / 4); // This works better. // Explanation: Setting $Num_Groups = $Num_Stu div n always creates // enough groups so that each group can have at least n members. // (Because, $Num_Stu div n <= $Num_Stu / n, so $Num_Stu / $Num_Groups >= n). // If there are less than n students in the class, then, even though // formally there are not enough, we want to have one group anyway. // List of groups with list of associated states. $Groups = []; $NeededRoles = []; // Parallel array; roles yet to be covered. for ($i = 1; $i <= $Num_Groups; $i++) { $Groups[$i] = []; $NeededRoles[$i] = array(1 => TRUE, 2 => TRUE, 3 => TRUE, 4 => TRUE); // Bitfield for options 1, 2, 3, 4. } // StuCapabilities is an unchanging copy of the input, slightly different format. // RolePools is a list of decreasing pools from which student ids are drawn. // One "pool" per role. $StuCapabilities = array(); foreach (array_keys($input_data) as $S_i) { $StuCapabilities[$S_i] = []; } $RolePools = array(1 => [], 2 => [], 3 => [], 4 => []); // Fill StuCapabilities and RolePools. foreach ($input_data as $S_i => $RoleRespList_i) { foreach ($RoleRespList_i as list($RoleNum_j, $RoleResp_j)) { if ($RoleResp_j) { $RolePools[$RoleNum_j][] = $S_i; $StuCapabilities[$S_i][] = $RoleNum_j; } } } // Step 2: Distribute students with rare roles. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Arbitrarily pick one of the smallest in RolePools. (Cred: Amal Murali @ StackOverflow) $PoolSizes = array_map('count', $RolePools); $MinPoolSize = min($PoolSizes); $MinIndex = array_flip($PoolSizes)[$MinPoolSize]; $RarePool =& $RolePools[$MinIndex]; // Alias, so use reference op. // Distribute the students who picked the chosen rare role. $NumToDistribute = min($MinPoolSize, $Num_Groups); for ($count = 1; $count <= $NumToDistribute; $count++) { $S = SugAlg::PickOne($RarePool); SugAlg::AddStudent($S, $Groups[$count], $StuCapabilities, $NeededRoles[$count], $RolePools); } // Step 3: Try to fill all roles for all groups. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // While there are groups that still needs roles // (roles which have not already been exhausted)... //REVISION-STEP: Try to use array_filter. while ($RemGrpIds = array_keys(array_map('SugAlg::HasNeededRoles', $NeededRoles), TRUE, TRUE)) { // For each of these groups... foreach ($RemGrpIds as $x) { $Groups_x =& $Groups[$x]; // Alias, so use reference op. $NeededRoles_x =& $NeededRoles[$x]; // Alias, so use reference op. // Pick a needed role (might be exhausted). $R = array_search(TRUE, $NeededRoles_x); // If there's still students for that role (not exhausted)... if (!empty($RolePools[$R])) { $S = SugAlg::PickOne($RolePools[$R]); SugAlg::AddStudent($S, $Groups_x, $StuCapabilities, $NeededRoles_x, $RolePools); } else { $NeededRoles_x[$R] = FALSE; } } } return $Groups; }
// ---------------------------------------------------------------------------- // Prepare input by mapping question numbers to role numbers. // ---------------------------------------------------------------------------- // Bijective map. $map_ques_to_role = array(15 => 1, 16 => 2, 17 => 3, 18 => 4); // Modify $student_list, applying the map to question numbers. foreach ($student_list as &$role_response_list) { foreach ($role_response_list as &$ques_resp_pair) { $question_num = $ques_resp_pair[0]; $ques_resp_pair[0] = $map_ques_to_role[$question_num]; } } // ---------------------------------------------------------------------------- // Invoke suggestion algorithm. // ---------------------------------------------------------------------------- $groups = SugAlg::compute_groups($student_list); // ---------------------------------------------------------------------------- // Add names. // ---------------------------------------------------------------------------- $query = "SELECT student_id, name FROM student_info"; $name_responses = DB::query($query); $names = array(); foreach ($name_responses as $name_record) { $student_id = (int) $name_record['student_id']; $name = $name_record['name']; $names[$student_id] = $name; } // StuId -> [StuId, Name] function add_name($stu_id) { global $names;