From: Warren Brown Date: 2004-10-05T02:40:07+09:00 Subject: Re: [QUIZ][SOLUTION] Secret Santas (#2) Well, I originally was going to go down the "collect by family" road and make the code deterministic, but for the intended data and intended use, the nondeterministic approach seemed better. Also, this allows for more "rules" to be added without changing the basic code. As an example, I added a rule for "don't allow trades" (where two people are each other's Santa). For testing purposes, I added a "display results" flag and a "send emails" flag. I also added an "override email" address/flag to send all emails to a single email address (while keeping the "From" and "To" headers correct). # Required libraries require 'net/smtp' # Configuration settings NO_FAMILIES = true NO_TRADES = true DISPLAY_RESULTS = true SEND_EMAILS = true SMTP_SERVER = 'yoursmtpserver.yourisp.com' FROM_EMAIL = 'yourfromemail@yourisp.com' OVERRIDE_EMAIL = 'youremail@yourisp.com' # nil to turn off EMAIL_BODY = %{%{\ From: "Secret Santa Club" <\#{FROM_EMAIL}> To: "\#{person.santa}" <\#{person.santa.email}> Subject: Secret Santa \#{person.santa.first_name}, You are Secret Santa for \#{person}. You can email \#{person.first_name} at: mailto:\#{person.email}. }} # The Person structure Person = Struct.new('Person',:first_name,:last_name,:email,:santa) class Person; def to_s; "#{first_name} #{last_name}"; end; end # Read people into array people = [] while line = gets first_name,last_name,email = line.scan(/(.*) (.*) <(.*)>/).flatten people << Person.new(first_name,last_name,email,nil) end # Make sure a solution is possible if people.length < (NO_TRADES ? 3 : 2) puts "No solution possible. Not enough people." exit(-1) end family = Hash.new(0) people.each { |person| family[person.last_name] += 1 } biggest_family = family.max { |a,b| a[1] <=> b[1] } if biggest_family[1] > people.length / 2 puts "No solution possible. Family #{biggest_family[0]} is more than half" + " of the group (#{biggest_family[1]} out of #{people.length})." exit(-1) end # Assign each person a random secret santa pool = people.dup people.each { |person| person.santa = pool.delete_at(rand(pool.length)) } # For each person who has an illegal santa, switch with someone else # Keep looping until no more illegal santas are found begin finished = true people.each do |person| if (person.santa == person) || (NO_FAMILIES && person.last_name == person.santa.last_name) || (NO_TRADES && person.santa.santa == person) finished = false new_person = people[rand(people.length)] person.santa,new_person.santa = new_person.santa,person.santa end end end until finished # Output results people.each do |person| if DISPLAY_RESULTS puts "#{person.santa} is secret santa for #{person}." end if SEND_EMAILS Net::SMTP.start(SMTP_SERVER) do |smtp| response = smtp.sendmail( eval(EMAIL_BODY), FROM_EMAIL, OVERRIDE_EMAIL || person.santa.email ) puts "Sent email to #{person.santa.email}, response = '#{response.chomp}'" end end end - Warren Brown