Hmm does not seem that the attachments are too readable on the Ruby Q.
site.
So please forgive me for posting them here again, inline. I do not
include the HTML plugin as it is not closely connected to the topic of
the Quiz.
Cheers
Robert
cat 124-magic-square.rb
vim: sts=2 sw=2 ft=ruby expandtab nu tw=0:
Usage = <<-EOS
usage:
ruby #{$0} [-t|–test] [-h|–html]
Prints Magic Squares for all indicated orders.
Indicating -t also tests the results.
EOS
loop do
case ARGV.first
when “-t”, “–test”
require ‘test-squares’
ARGV.shift
when “-h”, “–html”
require ‘html-output’
ARGV.shift
when “-?”, “–help”, nil
puts Usage
exit
when “–”
ARGV.shift && break
else
break
end
end
This is a default output module, another output
module called HTMLOutput is provided as an example
how to pull in an appropriate Output module
as plugin.
module Output
def to_s decoration = false
l = (@order*@order).to_s.size
return @data.map{ |line|
line.map{ |cell|
“%#{l}d” % cell
}.join(" “)
}.join(”\n") unless decoration
sep_line = "+" << ( "-" * l.succ.succ << "+" ) * @order
sep_line.dup << "\n" <<
@data.map{ | line | "| " << line.map{ |cell| "%#{l}d" % cell
}.join(" | “) << " |” }.
zip( [sep_line] * @order ).flatten.join("\n")
end
end
The usage of cursors is slowing down the program a little
bit but I feel it is still fast enough.
class Cursor
attr_reader :cpos, :lpos
def initialize order, lpos, cpos
@order = order
@lpos = lpos
@cpos = cpos
end
def move ldelta, cdelta
l = @lpos + ldelta
c = @cpos + cdelta
l %= @order
c %= @order
self.class.new @order, l, c
end
def next!
@cpos += 1
return if @cpos < @order
@cpos = 0
@lpos += 1
@lpos %= @order
end
end
This is where the work is done, like
testing and outputting and what was it?
Ah yes storing the data.
class SquareData
include Output
include HTMLOutput rescue nil
include TestSquare rescue nil
def initialize order
@order = order
@data = Array.new( @order ){ Array.new( @order ) { nil } }
end
def peek(i, j); @data[i][j] end
def poke(i, j, v); @data[i][j] = v end
def ; @data[c.lpos][c.cpos] end
def []=(c, v); @data[c.lpos][c.cpos] = v end
def each_subdiagonal
(@order/4).times do
| line |
(@order/4).times do
| col |
4.times do
| l |
4.times do
| c |
yield [ 4line + l, 4col + c ] if
l==c || l+c == 3
end
end # 4.times do
end # (@order/4).times do
end # (@order/4).times do
end
def siamese_order
model = self.class.new @order
last = @order*@order
@pos = Cursor.new @order, 0, @order/2
yield @pos.lpos, @pos.cpos, peek( @pos.lpos, @pos.cpos )
model[ @pos ] = true
2.upto last do
npos = @pos.move -1, +1
npos = @pos.move +1, 0 if model[ npos ]
model[ @pos = npos ] = true
yield @pos.lpos, @pos.cpos, peek( @pos.lpos, @pos.cpos )
end # @last.times do
end
end # class SquareData
The base class for Magic Squares it basically
is the result of factorizing the three classes
representing the three differnt cases, odd, even and
double even.
It’s singleton class is used as a class Factory for
the three implementing classes.
class Square
def to_s decoration = false
@data.to_s decoration
end
private
def initialize order
@order = order.to_i
@last = @order*@order
@data = SquareData.new @order
compute
@data.test rescue nil
end
end
The simplest case, the Siamese Order algorithm
is applied.
class OddSquare < Square
private
def compute
@pos = Cursor.new @order, 0, @order/2
@data[ @pos ] = 1
2.upto @last do
| n |
npos = @pos.move -1, +1
npos = @pos.move +1, 0 if @data[ npos ]
@data[ @pos = npos ] = n
end # @last.times do
end
end # class OddSquare
The Double Cross Algorithm is applied
to double even Squares.
class DoubleCross < Square
def compute
pos = Cursor.new @order, 0, 0
1.upto( @last ) do
| n |
@data[ pos ] = n
pos.next!
end # 1.upto( @last ) do
@data.each_subdiagonal do
| lidx, cidx |
@data.poke lidx, cidx, @last.succ - @data.peek( lidx, cidx )
end
end
end
And eventually we use the LUX algorithm of Conway for even
squares.
class FiatLux < Square
L = [ [0, 1], [1, 0], [1, 1], [0, 0] ]
U = [ [0, 0], [1, 0], [1, 1], [0, 1] ]
X = [ [0, 0], [1, 1], [1, 0], [0, 1] ]
def compute
half = @order / 2
lux_data = SquareData.new half
n = half/2
pos = Cursor.new half, 0, 0
n.succ.times do
half.times do
lux_data[ pos ] = L
pos.next!
end # half.times do
end # n.succ.times do
half.times do
lux_data[ pos ] = U
pos.next!
end # half.times do
lux_data.poke n, n, U
lux_data.poke n+1, n, L
2.upto(n) do
half.times do
lux_data[ pos ] = X
pos.next!
end
end # 2.upto(half) do
count = 1
lux_data.siamese_order do
| siam_row, siam_col, elem |
elem.each do
| r, c |
@data.poke 2*siam_row + r, 2*siam_col + c, count
count += 1
end # elem.each do
end # lux_data.siamese_order do
end
end # class FiatLux
class << Square
trying to call the ctors with consistent values only
protected :new
def Factory arg
arg = arg.to_i
case arg % 4
when 1, 3
OddSquare.new arg
when 0
DoubleCross.new arg
else
FiatLux.new arg
end
end
end
ARGV.each do
|arg|
puts Square::Factory( arg ).to_s( true )
puts
end
END
#########################################
#########################################
#207/15 > cat test-squares.rb
vim: sts=2 sw=2 ft=ruby expandtab nu tw=0:
module TestSquare
def assert cdt, msg
return $stderr.puts( “#{msg} . . . . . ok” ) if cdt
raise Exception, msg << “\n” << to_s
end
def test
dia1 = dia2 = 0
@order.times do
| idx |
dia1 += peek( idx, idx )
dia2 += peek( idx, -idx.succ )
end # @lines.each_with_index do
assert dia1==dia2, “Both diagonals”
@order.times do
| idx1 |
col_n = row_n = 0
@order.times do
| idx2 |
col_n += peek idx2, idx1
row_n += peek idx1, idx2
end
assert dia1 == col_n, “Col #{idx1}”
assert dia1 == row_n, “Row #{idx1}”
end # @lines.each_with_index do
end # def test
def is_ok?
dia1 = dia2 = 0
@order.times do
| idx |
dia1 += peek( idx, idx )
dia2 += peek( idx, -idx.succ )
end # @lines.each_with_index do
return false unless dia1==dia2
@order.times do
| idx1 |
col_n = row_n = 0
@order.times do
| idx2 |
col_n += peek idx2, idx1
row_n += peek idx1, idx2
end
return false unless dia1 == col_n
return false unless dia1 == row_n
end # @lines.each_with_index do
true
end
end
END