Here is a different solution. This first populates each ticket with
at least 1 entry. After that it treats the tickets as 18 rows and
populates it with the "max 5 in a row"-constraint. To avoid getting
stuck, it will swap out a random number if there already are 5
numbers in the row. This could cause a ticket to lose its minimum of
1 entry per column, so the initial entries in every ticket column are
prevented from being swapped out.
Finally the actual numbers are added instead of the placeholders used
up to this point.
####
class Housie
def initialize(housie_array)
@housie_array = housie_array
raise "Illegal size" unless @housie_array.size == 3
@housie_array.each { |row| raise "Illegal row in housie #
{row.inspect}" unless row.compact.size == 5 }
9.times do |col|
raise "Illegal column #{col}" unless @housie_array.collect { |
row| row[col] }.compact.size > 0
end
end
# Ugly code to display a ticket.
def to_s
@housie_array.inject("") do |sum, row|
sum + "+----" * 9 + "+\n" +
row.inject("|") { | sum, entry | sum + " #{"%2s" % entry} |" }
+ "\n"
end +
"+----" * 9 + "+"
end
def Housie.new_book
housies = Array.new(6) { Array.new(3) { Array.new(9) } }
columns = Array.new(9) { |col| Array.new(10, col) }
columns[0].shift
columns[8] << 8
# First make sure every book has at least one entry.
# These entires are fixed and can't be swapped out.
columns.each do |col|
housies.each do |housie|
housie.select { |row| row.compact.size < 5 }.sort_by
{ rand }.first[col.shift] = :fixed
end
end
# Merge all rows
all_rows = []
housies.each { |housie| housie.each { |row| all_rows << row } }
# Fill all rows, start with the one with fewest entries, resolve
ties randomly
while (columns.flatten.compact.size > 0) do
columns.select { |col| col.size > 0 }.each do |col|
all_rows.reject { |row| row[col.first] }.sort_by
{ rand }.each do |row|
break unless col.first
# A full row needs to have a value swapped out
if row.compact.size == 5
# Only try to swap if we have swappable entries
if row.member? :selected
removed_col = rand(9) while removed_col.nil? || row
[removed_col] != :selected;
row[removed_col] = nil
columns[removed_col] << removed_col
row[col.shift] = :selected
end
else
row[col.shift] = :selected
end
end
end
end
# Populate actual numbers
values = Array.new(9) { |col| Array.new(10) { |i| i + col * 10 } }
values[0].shift # remove 0
values[8] << 90 # add 90
values.each_with_index do |col, index|
col = col.sort_by { rand }
housies.each do |housie|
entries = housie.inject(0) { |sum, row| sum + (row[index] ?
1 : 0) }
values = col.slice!(0...entries).sort
housie.each { |row| row[index] = values.shift if row[index] }
end
end
housies.collect { |housie| Housie.new(housie) }
end
end