[QUIZ] Magic Squares (#124)

James Gray wrote:

···

On May 18, 2007, at 10:22 AM, Drew Olson wrote:

Here's my solution. It only works for odd-sized squares at the moment,
maybe I'll get ambitious later and go for the extra credit.

Drew:

Ruby Quiz has a "no spoiler period" rule. We request that users do
not share and spoiler material for the first 48 hours after a quiz is
released. This rule is at the top of every quiz. No big deal, but
please keep that in mind for the future.

James Edward Gray II

Doh! So sorry, totally spaced out on this one. My fault, won't happen
again.

--
Posted via http://www.ruby-forum.com/\.

>Robert Dober|

> BTW is there a book to be published one day about Ruby Quiz?
It is
http://pragmaticprogrammer.com/titles/fr_quiz/index.html

Or you meant _second_ book?

Why did nobody tell me??? :wink:
Yeah a second and a third and so on...

At least the idea was not a bad one :wink:

Cheers
Robert

···

On 5/20/07, I. P. <stack.tcpip@rambler.ru> wrote:

--
I. P. 2007-05-20T16:36

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

It is not clear to me to which solution you are referring, yes I have
tested with various larges sides ;).
Can I post the links to the algorithms for the interested or would
this be a spolier?

Cheers
Robert

···

On 5/20/07, Lloyd Linklater <lloyd@2live4.com> wrote:

I heard that this approach only works with every other odd sided square.
Did you test it with larger squares and does it still work? If not, is
there a further quiz for that? What about even sided squares?

Just wondering.

--
Posted via http://www.ruby-forum.com/\.

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

There's a very easy algorithm for odd size squares, which is why the quiz focused on this. Finding the even sizes is trickier, but not impossible, thus the extra credit.

James Edward Gray II

···

On May 20, 2007, at 8:38 AM, Lloyd Linklater wrote:

I heard that this approach only works with every other odd sided square.
Did you test it with larger squares and does it still work? If not, is
there a further quiz for that? What about even sided squares?

Just wondering.

This is my solution. It only does odd magic squares, but it can generate all eight versions (assuming there are only eight. It seemed that way to me.)

http://pastie.caboo.se/63130

- donald

# Ruby Quiz 124
# Donald Ball
# version 1.0

class MagicSquare
  SLANTS = [-1, 1]
  STARTS = [:top, :bottom, :left, :right]

  def initialize(n, slant=SLANTS[rand(SLANTS.length)], start=STARTS[rand(STARTS.length)])
    raise ArgumentError unless n > 0 && (n % 2) == 1
    raise ArgumentError unless SLANTS.include?(slant)
    raise ArgumentError unless STARTS.include?(start)
    @n = n
    @sum = (@n**3 + @n) / 2
    @values = Array.new(@n**2)
    if start == :top || start == :bottom
      c = @n / 2
      dc = slant
      ddc = 0
      if start == :top
        r = 0
        dr = -1
        ddr = 1
      else
        r = -1
        dr = 1
        ddr = -1
      end
    else
      r = @n / 2
      dr = slant
      ddr = 0
      if start == :left
        c = 0
        dc = -1
        ddc = 1
      else
        c = -1
        dc = 1
        ddc = -1
      end
    end
    (1..@n).each do |i|
      (1..@n).each do |j|
        self[r, c] = @n*(i-1) + j
        unless j==@n
          r += dr
          c += dc
        else
          r += ddr
          c += ddc
        end
      end
    end
  end

  def offset(r, c)
    (r % @n) * @n + (c % @n)
  end

  def [](r, c)
    @values[offset(r, c)]
  end

  def []=(r, c, v)
    @values[offset(r, c)] = v
  end

  def range
    (0..@n-1)
  end

  def col(c)
    range.map {|r| @values[c + r*@n]}
  end

  def cols
    range.map {|c| col(c)}
  end

  def row(r)
    @values[r*@n, @n]
  end

  def rows
    range.map {|r| row(r)}
  end

  def diagonals
    [range.map {|i| @values[i*(@n+1)]}, range.map {|i| @values[(@n-1)*(i+1)]}]
  end

  def valid?
    (rows + cols + diagonals).each do |chunk|
      return false unless chunk.inject {|sum, v| sum += v} == @sum
    end
    true
  end

  def to_s
    def ojoin(a, sep)
      sep + a.join(sep) + sep
    end
    l = (@n**2).to_s.length
    sep = "+#{'-'*(@n*(l+2) + (@n-1))}+\n"
    ojoin(rows.map {|row| ojoin(row.map {|v| sprintf(" %#{l}d ", v)}, '|') + "\n"}, sep)
  end
end

if $0 == __FILE__
  puts MagicSquare.new(ARGV[0].to_i) if ARGV.length == 1
end

The no spoiler period has passed. Post whatever you like.

James Edward Gray II

···

On May 20, 2007, at 9:57 AM, Robert Dober wrote:

Can I post the links to the algorithms for the interested or would
this be a spolier?

Hmm does not seem that the attachments are too readable on the Ruby Quiz 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] <Square Order List>

      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 [](c); @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 [ 4*line + l, 4*col + 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__

> Can I post the links to the algorithms for the interested or would
> this be a spolier?

The no spoiler period has passed. Post whatever you like.

:))
For those who did not search or find

Robert

···

On 5/20/07, James Edward Gray II <james@grayproductions.net> wrote:

On May 20, 2007, at 9:57 AM, Robert Dober wrote:

James Edward Gray II

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

http://www.rubyquiz.com/quiz115.html

:wink:

James Edward Gray II

···

On May 20, 2007, at 2:53 PM, Robert Dober wrote:

Hmm does not seem that the attachments are too readable on the Ruby Quiz site.