James G. wrote:
There are two levels of quiz difficulty to choose from. Given the above
rules
for the validity of tickets and books, then:
- Write a Ruby program which generates random individual tickets
or
- Write a Ruby program which generates random complete books of
tickets
Yay, I finally got time for this, my first try to submit one. It might
be a little tricky, but it does what it has to
#First of all, let’s look at the books.
#Assuming a symetrical distribution of tens, we have that for all nine
rows,
#tickets can have one of the following distributions of numbers in a
columnn:
def make_poss
[ [1, 1, 1, 2, 2, 2, 2, 2, 2],
[1, 1, 1, 1, 2, 2, 2, 2, 3],
[1, 1, 1, 1, 1, 2, 2, 3, 3],
[1, 1, 1, 1, 1, 1, 3, 3, 3] ]
end
#Don’t forget to put the numbers in the bag!
def make_container
initial = (0…8).to_a.collect{|n| ((10n)…(10(n+1) - 1)).to_a }
initial[0].delete(0); initial[-1].push(90)
initial
end
class Array
#Tricked so it adds only the non-nil elements
def sum
inject(0) {|sum, n| n.nil? ? sum : sum + n }
end
def column(n)
collect{|row| row[n]}
end
end
class Book < Array
attr_accessor :tickets, :distribution
def initialize
#Lets’s the fun begin. As stated before, a ticket can have on of 4
possible
#distribution of tickets (and they are ALL the possibilities).
#So, we start defining randomly the distributions of our tickets.
#Furthermore, we scramble the possibilities, so any row can have any
distribution provided.
super(Array.new(6).collect{|i| (poss =
make_poss)[rand(poss.size)].sort_by{rand} })
#Now we have to distribute our matrix. Because of the above, each
row sums 15, but we have to make
#each column sum 10.
make_distribution
#Now we adjust the numbers on each ten.
balance
@tickets = []
#And we're ready
make_tickets
end
#This method iterates each column, and accomodate the numbers until
the sum of each column is 10.
def make_distribution
for i in 0…9
s = column(i).sum
remaining = (10 - s).abs
if s != 10
#If the sum is different than 10, decrement one to the greater
#and increment it to the possible in the row, or viceversa.
#If that’s not possible, go onto the next row, and repeat if
necessary.
remaining.times do
index = 0
until gain(index, i, (s < 10 ? 1 : -1))
index += 1
index = 0 if index == 5
end
end
end
end
end
def display
@tickets.each {|ticket| ticket.display}
end
#Knowing the distribution of the tickets, they are done almost
automatically.
def make_tickets
container = make_container
each{ |row| @tickets << Ticket.new(row, container) }
end
#Returns the index where the increment occured, or nil if cannot be
done
def gain(row, column, num = 1)
item = self[row][column]
#We know a priori that the numbers must be between 1 and 3
return nil if (item == 3 && num == 1) or (item == 1 && num == -1)
#iterate over the array, starting from the right of the column
for i in (column + 1)...(self[row].size)
#Find the first element that can accept a loss of -num (or a gain)
if (1..3) === (self[row][i] - num)
#if so, increment and decrement the numbers.
self[row][column] += num
self[row][i] -= num
return i
end
end
return nil
end
#Balances the ticket distribution so the first row has 9 numbers and
#the last one 11, without affecting the sum.
def balance
for row in self
if row[0] > 1 and row[-1] < 3
row[0] -= 1
row[-1] += 1
break
end
end
end
end
class Ticket < Array
def initialize(distribution = (poss = make_poss)[rand(poss.size)],
container = make_container)
#When initializing, we first make the ticket ‘vertical’ so its
easier to keep track of the
#numbers in each row.
super(9); collect! {|row| []}
distribution.each_with_index do |distr, index|
choose = container[index]
distr.times do
#Exhausting possibilities
self[index] << choose.slice!(rand(choose.size))
end
end
collect! { |row| row.sort! }
make_format
end
def make_format
#Iterate over the colums
for i in 0…3
#Do this until we have 5 elements per column.
until (s = column(i).compact.length) == 5
#If the number of elements in the column is more than 5,
#move one randomly to the next place
if s > 5
x = rand(9)
self[x].insert(i, nil) unless self[x].length == 3
else
#If the number is less than four (that can only happen in the
second column),
#remove one nil
for row in self
if row[1] == nil and row[2] != nil
row[1], row[2] = row[2], nil
break
end
end
end
end
end
#Now we just transpose the ticket.
replace((0…3).collect{ |i| column(i) })
end
def display
print(top = “±—” * 9 + “+\n”)
each do |row|
row.each{ |number| print("|" + " " * (3 - number.to_s.length) +
number.to_s + " ") }
print “|\n”, top
end
puts
end
end
puts “Type B for a complete book, or T for a single ticket.\nType
anything else to exit”
while (option = gets.chomp) =~ /\A[BbTt]/
if option =~ /\A[Bb]/
#Now all we have to do is to create a new book…
book = Book.new
#and display it…
puts “BINGO!\n”
book.display
else
#Or we can make single tickets… for cheating… you know
puts “Today’s my lucky day”
x = Ticket.new
x.display
end
puts “Play again?”
end