I am trying to design a shift scheduling program. There are about a dozen potential users (+/- a few), all with specific vacation requirements. There are 4 types of shifts (role1, role2, role3, role4); each role must be covered by a person, a single person cannot cover multiple roles, and some users prefer certain shift sequences (eg. role1-role2-role4-role3 vs. role1-role2-role3-role4), though ultimately the shift sequences can be rearranged if it can't be accommodated within the schedule. Some users can't work certain shifts at all. Each user also has a maximum number of shifts - overall, there isn't much flexibility within the system, so I suspect only a limited number of possible valid solutions.
I've created an array that takes into account mandatory user vacations, so for each calendar date I know which users are in theory available for that shift (at baseline):
[2020-07-21] => Array
(
[0] => Array
(
[role1] => userA
[role2] => userB
[role3] => userC
[role4] => userD
)
[1] => Array
(
[role1] => userA
[role2] => userB
[role3] => userD
[role4] => userC
)
[2] => Array
(
[role1] => userA
[role2] => userC
[role3] => userB
[role4] => userD
)
Array is generated by (but this code hasn't had issues - it's when I try to call print_r(cartesian($solutionsArray)); where I run out of memory.
// Use cartesian product to pick one value from each role array, and then exclude arrays that don't have at least 4 unique users
foreach ($globalAvailabilityCalendarArray[$globalAvailabilityCalendarDate] as $role => $availableUsersArray) {
$cartesianProducts = $utilities->cartesian($globalAvailabilityCalendarArray[$globalAvailabilityCalendarDate]);
foreach ($cartesianProducts as $cartesianProduct) {
if(count(array_unique(array_values($cartesianProduct))) !== count($this->roles)) {
continue;
} else {
$solutions[] = $cartesianProduct;
}
}
}
$solutionsArray[$globalAvailabilityCalendarDate] = array_unique($solutions, SORT_REGULAR);
I have a few problems though:
For this time period, there are 63 work days to take into account. Some days only have 4 people available so the possible combinations are small, but some days may have 10+ available people which means hundreds of possible valid combinations. However, the scheduling requirements of each day depend on what combination has been selected for previous days so it doesn't go over an individual worker's maximum number of shifts. Thus, I am trying to sequentially iterate over each subarray until I find the first possible valid solution (and if possible, other valid solutions for users to compare against) - (eg. [2020-07-01][0] ... [2020-07-02][1]... then [2020-07-01][0] ... [2020][07-02][2]). I have tried calculating cartesian product (Finding cartesian product with PHP associative arrays) which worked for finding available combinations for a single day, but my script runs out of memory when applying it to the whole calendar as there are just too many possible sequences. Is there a less memory intensive alternative?
With my array structure, how can I prioritize shift sequences so that users have a good ratio of shifts (target is role1+role2+role4 / role3 = 2.5) and preferred shift sequence (eg. role1-role2-role3-role4 and avoiding sequential busy shifts like role2-role2-role2-role2-role1)?