--8323328-1660871641-1096917474382
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-1096917474382
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 f 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  ine.split %r/[<>]/
       raise "syntax error in <#{ line }>" unless
         name and address
       tokens  ame.strip.split %r/\s+/
       last, first  okens.pop, tokens.join(' ')
       name  first, last]
       self << [name, address]
     end
#}}}
   end
#}}}
end
class Family < Hash
#{{{
   class << self
#{{{
     def families last  il
#{{{
       @families || }
       if last
         @families[last] ||ash::new{|h,last| h[last] || amily::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  ash::new{|h,k| h[k]  ash::new}
       families.each do |last, members|
         members.each do |first, email|
           @pool[last][first]  mail
         end
       end
#}}}
     end
     def draw_name last, first 
#{{{
       not_in_family  pool.keys - [last]
       family  ot_in_family[rand(not_in_family.size)]

       members  pool[family].keys - [first]
       member  embers[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  mailList::new port

list.each do |name, email|
   Family[name.last][name.first]  mail
end

gift_pool  amily.gift_pool

Family.each do |last, members|
   members.each do |first, email|
     santa  ift_pool.draw_name last, first
     sname, semail  anta
     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-1096917474382
Content-Type: TEXT/PLAIN; charset=US-ASCII; name="santa.rb"
Content-Transfer-Encoding: BASE64
Content-ID: <Pine.LNX.4.60.0410041317540.3382 / harp.ngdc.noaa.gov>
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-1096917474382--
--8323328-1660871641-1096917474382--