My solution drops the numbers 1-90 into the tickets at random. If  
there are more than 3 numbers of the same column for a ticket, or it  
already has 15 numbers then it kicks out one of its other numbers at  
random. This number is put back to be distributed to some other ticket.

After all the numbers are distributed, each Housie generates itself  
using the values it has been given. It will add one number at a time  
to the row with the least amount of numbers. If two rows have the  
same amount of numbers then their positions will be chosen randomly.

I also included a method to generate a single ticket.

The code got a bit messier than I'd like it, but it avoids brute  
force-generation and seem to generate tickets quickly.


/Christoffer


#####

class Housie

   def initialize
     @colset = Array.new(9) { [] }
     @numbers = 0
   end

   # Push a number to this ticket.
   #
   # If this number can't fit with the numbers already in this  
housie, we return
   # one of the old numbers in the housie that we removed to make  
this number fit.
   #
   def push(number)
     raise "Tried to push to generated housie ticket" if @housie
     column = number == 90 ? 8 : number / 10
     @colset[column] << number
     if @colset[column].size == 4
       @colset[column].shift
     elsif @numbers == 15
       value = @colset[rand(9)].shift while value.nil?
       value
     else
       @numbers += 1
       nil
     end
   end

   # Generates a ticket from added data
   # Since we have 15 numbers, not more than 3 of each column type we  
know we
   # can create a ticket, but we want a randomized look to it.
   def generate
     raise "Not enough data to generate ticket" unless complete?
     @housie = Array.new(3) { Array.new(9) }
     (0..8).sort_by { rand }.each do |column|
       @colset[column].size.times do
         rows = @housie.sort_by { rand }.sort { |row1, row2|  
row1.compact.size <=> row2.compact.size }
         rows.shift until rows.first[column].nil?
         rows.first[column] = true
       end
     end
     9.times do |column|
       @colset[column].sort!
       @housie.each { |row| row[column] = @colset[column].shift if row 
[column] }
     end
     self
   end

   # Ugly code to display a ticket.
   def to_s
     return "Not valid" unless @housie
     @housie.inject("") do |sum, row|
       sum + "+----" * 9 + "+\n" +
       row.inject("|") { | sum, entry | sum + " #{"%2s" % entry} |" }  
+ "\n"
     end +
     "+----" * 9 + "+"
   end

   def complete?
     @numbers == 15
   end

   def Housie.new_book
     housies = Array.new(6) { Housie.new }
     numbers = (1..90).to_a
     while numbers.size > 0 do
       pushed_out = housies[rand(6)].push(numbers.shift)
       numbers << pushed_out if pushed_out
     end
     housies.collect { |housie| housie.generate }
   end

   def Housie.new_ticket
     housie = Housie.new
     random_numbers = (1..90).sort_by { rand }
     until housie.complete?
       returned = housie.push random_numbers.shift
       random_numbers << returned if returned
     end
     housie.generate
   end

end


puts "A book of tickets:"
Housie.new_book.each_with_index { |housie, index| puts "Ticket # 
{index + 1}"; puts housie.to_s }

puts "A single ticket:"
puts Housie.new_ticket.to_s