From: "Ara.T.Howard" Date: 2004-10-05T04:24:54+09:00 Subject: [SOLUTION] Secret Santas (#2) --8323328-1660871641-1096917474=:3382 Content-Type: MULTIPART/MIXED; BOUNDARY="8323328-1660871641-1096917474=:3382" This message is in MIME format. The first part should be readable text, while the remaining parts are likely unreadable without MIME-aware tools. --8323328-1660871641-1096917474=:3382 Content-Type: TEXT/PLAIN; charset=US-ASCII; format=flowed this solution parses stdin or an input file. the input may contain blank line and comments. the input is first hashed last name -> first name -> email using the Family class. next a Family::GiftPool for the current state of the Family class is created and all families and their members iterated over; the pool's 'draw_name' method randomly choses a santa not in the same family and not the same person - the selection is destructive, meaning after someone has been chosen they can no longer be chosen again. this method requires one pass over the data (not counting the copy) and no sorting. mailing also included. #!/usr/bin/env ruby require 'net/smtp' class EmailList < Array #{{{ def initialize port #{{{ buf = if port.respond_to? 'read' port.read else "#{ port }" end parse_buf buf #}}} end def parse_buf buf #{{{ buf.each do |line| line.gsub! %r/^\s*|\s*$|#.*$/, '' next if line.empty? name, address = line.split %r/[<>]/ raise "syntax error in <#{ line }>" unless name and address tokens = name.strip.split %r/\s+/ last, first = tokens.pop, tokens.join(' ') name = [first, last] self << [name, address] end #}}} end #}}} end class Family < Hash #{{{ class << self #{{{ def families last = nil #{{{ @families ||= {} if last @families[last] ||= Hash::new{|h,last| h[last] ||= Family::new} else @families end #}}} end alias [] families def each(*a, &b); families.each(*a, &b); end def gift_pool; GiftPool::new families; end #}}} end class GiftPool #{{{ def initialize families #{{{ @pool = Hash::new{|h,k| h[k] = Hash::new} families.each do |last, members| members.each do |first, email| @pool[last][first] = email end end #}}} end def draw_name last, first #{{{ not_in_family = @pool.keys - [last] family = not_in_family[rand(not_in_family.size)] members = @pool[family].keys - [first] member = members[rand(members.size)] name = [family, member] email = @pool[family][member] santa = [name, email] @pool[family].delete member @pool.delete family if @pool[family].empty? santa #}}} end #}}} end #}}} end module Mailer #{{{ def self.mail msg, to, from, opts = {} #{{{ Net::SMTP.start('localhost') do |smtp| email = '' opts.each do |k, v| email << "#{ k.capitalize }: #{ v }\r\n" end email << "\r\n#{ msg }" smtp.send_message email, from, to end #}}} end #}}} end port = (ARGV.empty? ? STDIN : open(ARGV.shift)) list = EmailList::new port list.each do |name, email| Family[name.last][name.first] = email end gift_pool = Family.gift_pool Family.each do |last, members| members.each do |first, email| santa = gift_pool.draw_name last, first sname, semail = santa msg = "you are the secret santa for <#{ sname.join ', ' }>" Mailer::mail msg, email, nil, 'subject' => 'secret santa' end end -a -- =============================================================================== | EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov | PHONE :: 303.497.6469 | A flower falls, even though we love it; | and a weed grows, even though we do not love it. | --Dogen =============================================================================== --8323328-1660871641-1096917474=:3382 Content-Type: TEXT/PLAIN; charset=US-ASCII; name="santa.rb" Content-Transfer-Encoding: BASE64 Content-ID: Content-Description: Content-Disposition: attachment; filename="santa.rb" IyEvdXNyL2Jpbi9lbnYgcnVieQ0KcmVxdWlyZSAnbmV0L3NtdHAnDQoNCmNs YXNzIEVtYWlsTGlzdCA8IEFycmF5DQoje3t7DQogIGRlZiBpbml0aWFsaXpl IHBvcnQNCiN7e3sNCiAgICBidWYgPQ0KICAgICAgaWYgcG9ydC5yZXNwb25k X3RvPyAncmVhZCcNCiAgICAgICAgcG9ydC5yZWFkDQogICAgICBlbHNlDQog ICAgICAgICIjeyBwb3J0IH0iDQogICAgICBlbmQNCiAgICBwYXJzZV9idWYg YnVmDQojfX19DQogIGVuZA0KICBkZWYgcGFyc2VfYnVmIGJ1Zg0KI3t7ew0K ICAgIGJ1Zi5lYWNoIGRvIHxsaW5lfA0KICAgICAgbGluZS5nc3ViISAlci9e XHMqfFxzKiR8Iy4qJC8sICcnDQogICAgICBuZXh0IGlmIGxpbmUuZW1wdHk/ DQogICAgICBuYW1lLCBhZGRyZXNzID0gbGluZS5zcGxpdCAlci9bPD5dLyAN CiAgICAgIHJhaXNlICJzeW50YXggZXJyb3IgaW4gPCN7IGxpbmUgfT4iIHVu bGVzcw0KICAgICAgICBuYW1lIGFuZCBhZGRyZXNzDQogICAgICB0b2tlbnMg PSBuYW1lLnN0cmlwLnNwbGl0ICVyL1xzKy8NCiAgICAgIGxhc3QsIGZpcnN0 ID0gdG9rZW5zLnBvcCwgdG9rZW5zLmpvaW4oJyAnKQ0KICAgICAgbmFtZSA9 IFtmaXJzdCwgbGFzdF0gDQogICAgICBzZWxmIDw8IFtuYW1lLCBhZGRyZXNz XQ0KICAgIGVuZA0KI319fQ0KICBlbmQNCiN9fX0NCmVuZA0KY2xhc3MgRmFt aWx5IDwgSGFzaA0KI3t7ew0KICBjbGFzcyA8PCBzZWxmDQoje3t7DQogICAg ZGVmIGZhbWlsaWVzIGxhc3QgPSBuaWwNCiN7e3sNCiAgICAgIEBmYW1pbGll cyB8fD0ge30NCiAgICAgIGlmIGxhc3QNCiAgICAgICAgQGZhbWlsaWVzW2xh c3RdIHx8PQ0KICAgICAgICAgIEhhc2g6Om5ld3t8aCxsYXN0fCBoW2xhc3Rd IHx8PSBGYW1pbHk6Om5ld30gDQogICAgICBlbHNlDQogICAgICAgIEBmYW1p bGllcw0KICAgICAgZW5kDQojfX19DQogICAgZW5kDQogICAgYWxpYXMgW10g ZmFtaWxpZXMNCiAgICBkZWYgZWFjaCgqYSwgJmIpOyBmYW1pbGllcy5lYWNo KCphLCAmYik7IGVuZA0KICAgIGRlZiBnaWZ0X3Bvb2w7IEdpZnRQb29sOjpu ZXcgZmFtaWxpZXM7IGVuZA0KI319fQ0KICBlbmQNCiAgY2xhc3MgR2lmdFBv b2wNCiN7e3sNCiAgICBkZWYgaW5pdGlhbGl6ZSBmYW1pbGllcw0KI3t7ew0K ICAgICAgQHBvb2wgPSBIYXNoOjpuZXd7fGgsa3wgaFtrXSA9IEhhc2g6Om5l d30gDQogICAgICBmYW1pbGllcy5lYWNoIGRvIHxsYXN0LCBtZW1iZXJzfA0K ICAgICAgICBtZW1iZXJzLmVhY2ggZG8gfGZpcnN0LCBlbWFpbHwNCiAgICAg ICAgICBAcG9vbFtsYXN0XVtmaXJzdF0gPSBlbWFpbA0KICAgICAgICBlbmQN CiAgICAgIGVuZA0KI319fQ0KICAgIGVuZA0KICAgIGRlZiBkcmF3X25hbWUg bGFzdCwgZmlyc3QgDQoje3t7DQogICAgICBub3RfaW5fZmFtaWx5ID0gQHBv b2wua2V5cyAtIFtsYXN0XQ0KICAgICAgZmFtaWx5ID0gbm90X2luX2ZhbWls eVtyYW5kKG5vdF9pbl9mYW1pbHkuc2l6ZSldDQoNCiAgICAgIG1lbWJlcnMg PSBAcG9vbFtmYW1pbHldLmtleXMgLSBbZmlyc3RdDQogICAgICBtZW1iZXIg PSBtZW1iZXJzW3JhbmQobWVtYmVycy5zaXplKV0gDQoNCiAgICAgIG5hbWUg PSBbZmFtaWx5LCBtZW1iZXJdDQogICAgICBlbWFpbCA9IEBwb29sW2ZhbWls eV1bbWVtYmVyXQ0KICAgICAgc2FudGEgPSBbbmFtZSwgZW1haWxdDQoNCiAg ICAgIEBwb29sW2ZhbWlseV0uZGVsZXRlIG1lbWJlciANCiAgICAgIEBwb29s LmRlbGV0ZSBmYW1pbHkgaWYgQHBvb2xbZmFtaWx5XS5lbXB0eT8NCg0KICAg ICAgc2FudGENCiN9fX0NCiAgICBlbmQNCiN9fX0NCiAgZW5kDQojfX19DQpl bmQNCm1vZHVsZSBNYWlsZXINCiN7e3sNCiAgZGVmIHNlbGYubWFpbCBtc2cs IHRvLCBmcm9tLCBvcHRzID0ge30gDQoje3t7DQogICAgTmV0OjpTTVRQLnN0 YXJ0KCdsb2NhbGhvc3QnKSBkbyB8c210cHwNCiAgICAgIGVtYWlsID0gJycg DQogICAgICBvcHRzLmVhY2ggZG8gfGssIHZ8DQogICAgICAgIGVtYWlsIDw8 ICIjeyBrLmNhcGl0YWxpemUgfTogI3sgdiB9XHJcbiINCiAgICAgIGVuZA0K ICAgICAgZW1haWwgPDwgIlxyXG4jeyBtc2cgfSINCiAgICAgIHNtdHAuc2Vu ZF9tZXNzYWdlIGVtYWlsLCBmcm9tLCB0bw0KICAgIGVuZA0KI319fQ0KICBl bmQNCiN9fX0NCmVuZA0KDQoNCnBvcnQgPSAoQVJHVi5lbXB0eT8gPyBTVERJ TiA6IG9wZW4oQVJHVi5zaGlmdCkpDQoNCmxpc3QgPSBFbWFpbExpc3Q6Om5l dyBwb3J0DQoNCmxpc3QuZWFjaCBkbyB8bmFtZSwgZW1haWx8DQogIEZhbWls eVtuYW1lLmxhc3RdW25hbWUuZmlyc3RdID0gZW1haWwNCmVuZA0KDQpnaWZ0 X3Bvb2wgPSBGYW1pbHkuZ2lmdF9wb29sIA0KDQpGYW1pbHkuZWFjaCBkbyB8 bGFzdCwgbWVtYmVyc3wNCiAgbWVtYmVycy5lYWNoIGRvIHxmaXJzdCwgZW1h aWx8DQogICAgc2FudGEgPSBnaWZ0X3Bvb2wuZHJhd19uYW1lIGxhc3QsIGZp cnN0DQogICAgc25hbWUsIHNlbWFpbCA9IHNhbnRhIA0KICAgIG1zZyA9ICJ5 b3UgYXJlIHRoZSBzZWNyZXQgc2FudGEgZm9yIDwjeyBzbmFtZS5qb2luICcs ICcgfT4iDQogICAgTWFpbGVyOjptYWlsIG1zZywgZW1haWwsIG5pbCwgJ3N1 YmplY3QnID0+ICdzZWNyZXQgc2FudGEnDQogIGVuZA0KZW5kDQo= --8323328-1660871641-1096917474=:3382-- --8323328-1660871641-1096917474=:3382--