/** * Distribute seats for several courses in a course set using the given * user priorities. * * @param CourseSet $courseSet The course set containing the courses * that seats shall be distributed for. * @see CourseSet */ private function distributeByPriorities($courseSet) { Log::DEBUG('start seat distribution for course set: ' . $courseSet->getId()); $limited_admission = $courseSet->getAdmissionRule('LimitedAdmission'); //all users with their priorities $claiming_users = AdmissionPriority::getPriorities($courseSet->getId()); //all users which have bonus/malus $factored_users = $courseSet->getUserFactorList(); //all users with their max number of courses $max_seats_users = array_combine(array_keys($claiming_users), array_map(function ($u) use($limited_admission) { return $limited_admission->getMaxNumberForUser($u); }, array_keys($claiming_users))); //unlucky users get a bonus for the next round $bonus_users = array(); //users / courses für later waitlist distribution $waiting_users = array(); //number of already distributed seats for users $distributed_users = array(); $prio_mapper = function ($users, $course_id) use($claiming_users) { $mapper = function ($u) use($course_id) { return isset($u[$course_id]) ? $u[$course_id] : null; }; return array_filter(array_map($mapper, array_intersect_key($claiming_users, array_flip($users)))); }; //sort courses by highest count of prio 1 applicants $stats = AdmissionPriority::getPrioritiesStats($courseSet->getId()); $courses = array_map(function ($a) { return $a['h']; }, $stats); arsort($courses, SORT_NUMERIC); $max_prio = AdmissionPriority::getPrioritiesMax($courseSet->getId()); //count already manually distributed places $distributed_users = $this->countParticipatingUsers(array_keys($courses), array_keys($claiming_users)); Log::DEBUG('already distributed users: ' . print_r($distributed_users, 1)); //walk through all prios with all courses foreach (range(1, $max_prio) as $current_prio) { foreach (array_keys($courses) as $course_id) { $current_claiming = array(); $course = Course::find($course_id); $free_seats = $course->getFreeSeats(); //find users with current prio for this course, if they still need a place foreach ($claiming_users as $user_id => $prio_courses) { if ($prio_courses[$course_id] == $current_prio && $distributed_users[$user_id] < $max_seats_users[$user_id]) { //exclude participants if (!$course->getParticipantStatus($user_id)) { $current_claiming[$user_id] = 1; if (isset($factored_users[$user_id])) { $current_claiming[$user_id] *= $factored_users[$user_id]; } } else { Log::DEBUG(sprintf('user %s is already %s in course %s, ignoring', $user_id, $course->getParticipantStatus($user_id), $course->id)); } } } //give maximum bonus to users which were unlucky before foreach (array_keys($current_claiming) as $user_id) { if ($bonus_users[$user_id] > 0) { $current_claiming[$user_id] = $bonus_users[$user_id] * count($current_claiming) + 1; $bonus_users[$user_id]--; } } Log::DEBUG(sprintf('distribute %s seats on %s claiming with prio %s in course %s', $free_seats, count($current_claiming), $current_prio, $course->id)); Log::DEBUG('users to distribute: ' . print_r($current_claiming, 1)); $current_claiming = $this->rollTheDice($current_claiming); Log::DEBUG('the die is cast: ' . print_r($current_claiming, 1)); $chosen_ones = array_slice(array_keys($current_claiming), 0, $free_seats); Log::DEBUG('chosen ones: ' . print_r($chosen_ones, 1)); $this->addUsersToCourse($chosen_ones, $course, $prio_mapper($chosen_ones, $course->id)); foreach ($chosen_ones as $one) { $distributed_users[$one]++; } if ($free_seats < count($current_claiming)) { $remaining_ones = array_slice(array_keys($current_claiming), $free_seats); foreach ($remaining_ones as $one) { $bonus_users[$one]++; $waiting_users[$current_prio][$course_id][] = $one; } } } } //distribute to waitlists if applicable Log::DEBUG('waiting list: ' . print_r($waiting_users, 1)); foreach ($waiting_users as $current_prio => $current_prio_waiting_courses) { foreach ($current_prio_waiting_courses as $course_id => $users) { $users = array_filter($users, function ($user_id) use($distributed_users, $max_seats_users) { return $distributed_users[$user_id] < $max_seats_users[$user_id]; }); $course = Course::find($course_id); Log::DEBUG(sprintf('distribute waitlist of %s with prio %s in course %s', count($users), $current_prio, $course->id)); if (!$course->admission_disable_waitlist) { if ($course->admission_waitlist_max) { $free_seats_waitlist = $course->admission_waitlist_max - $course->getNumWaiting(); $free_seats_waitlist = $free_seats_waitlist < 0 ? 0 : $free_seats_waitlist; } else { $free_seats_waitlist = count($users); } $waiting_list_ones = array_slice($users, 0, $free_seats_waitlist); Log::DEBUG('waiting list ones: ' . print_r($waiting_list_ones, 1)); $this->addUsersToWaitlist($waiting_list_ones, $course, $prio_mapper($waiting_list_ones, $course->id)); foreach ($waiting_list_ones as $one) { $distributed_users[$one]++; } } else { $free_seats_waitlist = 0; } if ($free_seats_waitlist < count($users)) { $remaining_ones = array_slice($users, $free_seats_waitlist); Log::DEBUG('remaining ones: ' . print_r($remaining_ones, 1)); $this->notifyRemainingUsers($remaining_ones, $course, $prio_mapper($remaining_ones, $course->id)); } } } }
/** * Gets courses fulfilling the given condition. * * @param String $seminare_condition SQL condition */ function get_courses($seminare_condition) { global $perm, $user; list($institut_id, $all) = explode('_', $this->current_institut_id); // Prepare count statements $query = "SELECT count(*)\n FROM seminar_user\n WHERE seminar_id = ? AND status IN ('user', 'autor')"; $count0_statement = DBManager::get()->prepare($query); $query = "SELECT SUM(status = 'accepted') AS count2,\n SUM(status = 'awaiting') AS count3\n FROM admission_seminar_user\n WHERE seminar_id = ?\n GROUP BY seminar_id"; $count1_statement = DBManager::get()->prepare($query); $parameters = array(); $sql = "SELECT seminare.seminar_id,seminare.Name as course_name,seminare.VeranstaltungsNummer as course_number,\n admission_prelim, admission_turnout,seminar_courseset.set_id\n FROM seminar_courseset\n INNER JOIN courseset_rule csr ON csr.set_id=seminar_courseset.set_id AND csr.type='ParticipantRestrictedAdmission'\n INNER JOIN seminare ON seminar_courseset.seminar_id=seminare.seminar_id\n "; if ($institut_id == 'all' && $perm->have_perm('root')) { $sql .= "WHERE 1 {$seminare_condition} "; } elseif ($all == 'all') { $sql .= "INNER JOIN Institute USING (Institut_id)\n WHERE Institute.fakultaets_id = ? {$seminare_condition}\n "; $parameters[] = $institut_id; } else { $sql .= "WHERE seminare.Institut_id = ? {$seminare_condition}\n "; $parameters[] = $institut_id; } $sql .= "GROUP BY seminare.Seminar_id ORDER BY seminar_courseset.set_id, seminare.Name"; $statement = DBManager::get()->prepare($sql); $statement->execute($parameters); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { $seminar_id = $row['seminar_id']; $ret[$seminar_id] = $row; $count0_statement->execute(array($seminar_id)); $count = $count0_statement->fetchColumn(); $ret[$seminar_id]['count_teilnehmer'] = $count; $count1_statement->execute(array($seminar_id)); $counts = $count1_statement->fetch(PDO::FETCH_ASSOC); $ret[$seminar_id]['count_prelim'] = (int) $counts['count2']; $ret[$seminar_id]['count_waiting'] = (int) $counts['count3']; $cs = new CourseSet($row['set_id']); $ret[$seminar_id]['cs_name'] = $cs->getName(); $ret[$seminar_id]['distribution_time'] = $cs->getSeatDistributionTime(); if ($ta = $cs->getAdmissionRule('TimedAdmission')) { $ret[$seminar_id]['start_time'] = $ta->getStartTime(); $ret[$seminar_id]['end_time'] = $ta->getEndTime(); } if (!$cs->hasAlgorithmRun()) { $ret[$seminar_id]['count_claiming'] = $cs->getNumApplicants(); } } return $ret; }