I am working on making a site which outputs all possible combinations of Secret Santa Pairs. I currently have this working by having the site visitor input the participants and any known couples. Once this is done, the visitor simply clicks the 'Calculate' button and it outputs the unique pairs (minus know couples - because they don't want to be paired), and the unique combinations of unique pairs.
Current Implementation
To achieve this, I have written what I believe to be an efficient algorithm to generate the unique pairs (minus the known couples):
function generateUniquePairs($participants, $known_couples) {
$participantCount = count($participants);
$pairs = [];
$pos = 0;
for ($i = 0; $i < $participantCount; $i++) {
for ($j = $i + 1; $j < $participantCount; $j++) {
$tempPair = [$participants[$i], $participants[$j]];
if ($known_couples) {
$isKnowCouple = false;
foreach($known_couples as $index => $known_couple) {
if(($tempPair[0] === $known_couple[0] && $tempPair[1] === $known_couple[1]) || ($tempPair[0] === $known_couple[1] && $tempPair[1] === $known_couple[0])) {
$isKnowCouple = true;
};
};
if ($isKnowCouple === false) {
$pairs[$pos++] = $tempPair;
}
} else {
$pairs[$pos++] = $tempPair;
}
}
}
return $pairs;
}
Which I then pass to a modified (and not efficient) \$\binom{n}{k}\$ algorithm:
$result = array();
$combination = array();
function generateUniqueCombinations(array $unique_pairs, $choose)
{
global $result, $combination;
$n = count($unique_pairs);
function inner($start, $choose_, $arr, $n)
{
global $result, $combination;
if ($choose_ == 0) {
$combination_count = count($combination);
$participantCounts = new stdClass();
$hasDuplicates = false;
for ($p = 0; $p < $combination_count; $p++) {
if (!$hasDuplicates) {
if (property_exists($participantCounts, $combination[$p][0])) {
$hasDuplicates = true;
} else {
$participantCounts->{$combination[$p][0]} = 1;
};
if (!$hasDuplicates) {
if (property_exists($participantCounts, $combination[$p][1])) {
$hasDuplicates = true;
} else {
$participantCounts->{$combination[$p][1]} = 1;
};
};
};
};
if (!$hasDuplicates) {
array_push($result, $combination);
};
$hasDuplicates = false;
} else {
for ($i = $start; $i <= $n - $choose_; ++$i) {
array_push($combination, $arr[$i]);
inner($i + 1, $choose_ - 1, $arr, $n);
array_pop($combination);
}
}
}
inner(0, $choose, $unique_pairs, $n);
return $result;
}
Example Scenario
The following participant list and know couples:
$participants = ["Tom", "Leanne", "Connor", "Sophie", "Tony", "Anita"];
$known_couples = ["Tom", "Leanne"];
$unique_pairs = generateUniquePairs($participants, $known_couples);
$unique_combinations = generateUniqueCombinations($unique_pairs, count($participant) / 2);
should ouput:
// $unique_pairs
[["Tom","Connor"],["Tom","Sophie"],["Tom","Tony"],["Tom","Anita"],["Leanne","Connor"],["Leanne","Sophie"],["Leanne","Tony"],["Leanne","Anita"],["Connor","Sophie"],["Connor","Tony"],["Connor","Anita"],["Sophie","Tony"],["Sophie","Anita"],["Tony","Anita"]]
// $unique_combinations
[[["Tom","Leanne"],["Anita","Tony"]],[["Tom","Anita"],["Leanne","Tony"]],[["Tom","Tony"],["Leanne","Anita"]]]
The Problem
My code works, but if you use a list of participants > 12, it generates an array of unique pairs of length > 66
, which means when it gets to running generateUniqueCombinations($unique_pairs, $choose)
, it times out.
I understand the problem is that my code is doing a lot of throw away processing due to it iterating through hundreds of thousands of potential combinations unnecessarily because those combinations are not viable due to them containing the same person more than once.
Where I need help
I would really like to understand if it is possible to skip past those non-viable combinations mathematically so there is no need to count participants within combinations and unnecessarily iterate.
I really would like my site to be more useful than being able to generate combinations of pairs for only <= 12 participants...
Ds\Set
class? It supports operations like set difference and it shouldn't be hard to create an extension that calculates the cartesian product. \$\endgroup\$$known_couples = ["Tom", "Leanne"];
but in the output the first set of pairs is:["Tom","Leanne"]
Can you clarify why that is a valid result? Also, I don't see how you can scale up your$known_couples
array with that single-dim structure. Shouldn't the structure be an array of two-element arrays by default? \$\endgroup\$