Created
September 26, 2015 20:06
-
-
Save DonaldHays/70a779c56e3a537e8475 to your computer and use it in GitHub Desktop.
Solve's the Secret Santa problem at http://inessential.com/2015/09/25/a_secret_santa_solution
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
/// Represents a person for the purposes of Secret Santa pairing. | |
struct Person: Equatable, CustomStringConvertible { | |
/// The person's given name. | |
var givenName: String | |
/// The person's family name. | |
var familyName: String | |
/// The person's email address. | |
var email: String | |
var description: String { | |
return "\(givenName) \(familyName)" | |
} | |
/// Returns true if the receiver can be a Secret Santa for another person, | |
/// or false if they cannot. Someone can be another person's Secret Santa | |
/// if the two people are not the same person, and if they aren't from the | |
/// same family. | |
func canGift(other: Person) -> Bool { | |
// Someone cannot gift themselves, and they cannot gift another member | |
// of the same family. Both rules are satisfied by checking family name. | |
return familyName != other.familyName | |
} | |
} | |
func == (lhs: Person, rhs: Person) -> Bool { | |
return lhs.givenName == rhs.givenName && lhs.familyName == rhs.familyName && lhs.email == rhs.email | |
} | |
extension Array { | |
/// Returns a random element from the receiver, or `nil` if the receiver is | |
/// empty. | |
var any: (Int, Element)? { | |
if count == 0 { | |
return nil | |
} | |
let index = Int(arc4random_uniform(UInt32(count))) | |
return (index, self[index]) | |
} | |
/// Returns a copy of the receiver that excludes the element at the | |
/// specified index. | |
func withoutElementAtIndex(index: Int) -> [Element] { | |
var newElements = self | |
newElements.removeAtIndex(index) | |
return newElements | |
} | |
} | |
/// Picks Secret Santa matches by pairing gifters with recipients. Returns an | |
/// array of matches such that no gifter will be matched with themself, and no | |
/// gifter will be matched with another member of the same family. If there is | |
/// no valid set of matches, returns `nil`. | |
/// | |
/// This method solves the problem recursively by picking a random gifter, and | |
/// then attempting to match against random recipients, seeing if the remaining | |
/// sets of gifters and recipients can produce a valid list of matches. If a | |
/// choice for a gifter ultimately cannot be matched with any candidate | |
/// recipient, then the problem is unsolvable. | |
func match(gifters gifters: [Person], recipients: [Person]) -> [(gifter: Person, recipient: Person)]? { | |
// Once there are no more gifters, we have picked everyone, return empty | |
// array (indicating successful end of chain) | |
guard gifters.count > 0 else { | |
return [] | |
} | |
// Grab a random gifter. | |
guard let (gifterIndexInGifters, gifter) = gifters.any else { | |
fatalError("Should've been able to grab a gifter that exists in gifters. Programming error.") | |
} | |
let remainingGifters = gifters.withoutElementAtIndex(gifterIndexInGifters) | |
// Make a list of candidate giftable recipients, and pick from the list | |
// randomly, seeing if the remaining gifters and recipients can validly | |
// match. | |
var candidates = recipients.filter { gifter.canGift($0) } | |
while candidates.count > 0 { | |
// Pick the candidate. | |
guard let (candidateIndexInCandidates, candidate) = candidates.any, candidateIndexInRecipients = recipients.indexOf(candidate) else { | |
fatalError("Should've been able to grab a candidate that exists in candidates and recipients. Programming error.") | |
} | |
// Once we pick a candidate, either we will be able to make valid | |
// matches from the remaining candidates, or this candidate cannot be | |
// matched with this gifter. Either way, remove the candidate from the | |
// candidate list. | |
candidates.removeAtIndex(candidateIndexInCandidates) | |
let remainingRecipients = recipients.withoutElementAtIndex(candidateIndexInRecipients) | |
// If calling match on the remaining gifters and recipients produces an | |
// array, we have a valid match, otherwise the match is invalid and we | |
// should try the next candidate. | |
if let matches = match(gifters: remainingGifters, recipients: remainingRecipients) { | |
return [(gifter: gifter, recipient: candidate)] + matches | |
} | |
} | |
// If we exhaust all candidates, then there are no valid matches for our | |
// gifter. We don't have to try any other gifters because if even one can't | |
// match, the problem we've been given is unsolvable. | |
return nil | |
} | |
// Not exactly parsing input from standard in, but that's not the interesting | |
// part of this problem anyway. | |
let people = [ | |
Person(givenName: "Luke", familyName: "Skywalker", email: "luke@theforce.net"), | |
Person(givenName: "Leia", familyName: "Skywalker", email: "leia@therebellion.org"), | |
Person(givenName: "Toula", familyName: "Portokalos", email: "toula@manhunter.org"), | |
Person(givenName: "Gus", familyName: "Portokalos", email: "gus@weareallfruit.net"), | |
Person(givenName: "Bruce", familyName: "Wayne", email: "bruce@imbatman.com"), | |
Person(givenName: "Virgil", familyName: "Brigman", email: "virgil@rigworkersunion.org"), | |
Person(givenName: "Lindsey", familyName: "Brigman", email: "lindsey@iseealiens.net") | |
] | |
// Match the array of people against itself. If the function returns nil, then | |
// the set of people we have is impossible to match. | |
guard let matches = match(gifters: people, recipients: people) else { | |
fatalError("Impossible to match Secret Santas") | |
} | |
// Print out the Secret Santa matches. | |
for match in matches { | |
print("\(match.gifter): You're \(match.recipient)'s Secret Santa!") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment