[SUMMARY] Tiling Turmoil (#33)

The quiz mentioned a mathematical proof (by Solomon Golomb), that could be used
in solving this problem. Most of the submissions used this technique and most
of them even figured it out without Google's help. Proof positive that coding
in Ruby increases the IQ.

Let's cover that proof. The easiest instance of the provided challenge is when
n = 1. No matter which square is missing from that, exactly one L-tromino fits
in the remaining squares. Here are all four possibilities:

  X 1 1 X 1 1 1 1
  1 1 1 1 X 1 1 X

Obviously, larger values on n require a little more thinking. Only a little
though, if we're clever. When n = 2, we can divide the board into quadrants to
get four 2 x 2 boards:

  _ _ | X _
  _ _ | _ _

···

---------
  _ _ | _ _
  _ _ | _ _

We can easily see that one of those is just the trivial case from above. The
key insight is that the other three quadrants could be made into the same
trivial case, with one well-placed L-tromino in the center:

  _ _ | X _
  _ 1 | _ _
  ---------
  _ 1 | 1 _
  _ _ | _ _

Now we have four simple problems to solve, no thought required:

  2 2 | X 3
  2 1 | 3 3
  ---------
  6 1 | 1 5
  6 6 | 5 5

What about n = 3 and beyond? Well, dividing n = 3 into quadrants, with another
well-placed center L-tromino, gives us four n = 2 problems. We can solved each
of those, as we did above. Then n = 4 can be divided into four n = 3 problems.
By now, you should see the pattern.

No matter how big the board gets, we can drop an L-tromino in the middle and
divide it into quadrants. We can keep reducing those sections into quadrants of
their own, until we get all the way back to a bunch of trivial n = 1 problems.

Here's a link to a nice graphical display of that proof, for those who still
need to see it in action:

  http://www.cut-the-knot.org/Curriculum/Geometry/Tromino.shtml

Jannis Harder's solution produced pretty HTML output similar to what you see at
the above link, but uses a different strategy to obtain the answers. You might
want to run it a few times, to examine that technique.

The solutions emulating the proof, were all fairly similar in function.
Differing mainly is code style. Let's break down one such solution by Dominik
Bathon. Here's the helper class from Dominik's code:

  #######################################################

  # A simple 2D array, the width and height are immutable once it is created.
  # #to_s accepts an optional block that can format the elements.
  class Array2D
          attr_reader :w, :h
          def initialize(w, h, defel=nil)
                  @w, @h=w, h
                  @array=Array.new(w*h, defel)
          end
          def [](x, y)
                  @array[(y % h) * w + (x % w)]
          end
          def []=(x, y, v)
                  @array[(y % h) * w + (x % w)]=v
          end

          def to_s
                  (0...h).collect { |y|
                          (0...w).collect { |x|
                                  v=self[x, y]
                                  block_given? ? yield(v) : v
                          }.join " "
                  }.join "\n"
          end
  end
  
  # ...

The comment tells you what we're looking at here, a two-dimensional Array. The
internal storage format (setup in initialize()) is really just a normal Array,
with some clever indexing that you can see in the []() and []=() methods.

The to_s() method is pretty interesting. You can see that is just generates
each x and y combination and feeds those to []() to get each square. A couple
of join()s at the end of the method create first rows and then a final String
from that, separating the squares with spaces and the rows with newlines.
There's a nice use of blocks here though. Each square is passed to a block (if
given), so that it may format the square for display.

Now we're ready to examine the solution class:

  # ...
  
  class TrominoTiler
          def initialize(n)
                  n=[1, n].max
                  # initialize the working array

                  @a=Array2D.new(1 << n, 1 << n)
          end

          def tile_with_empty(x, y)
                  @tilenum=0 # counter
                  tile_recursive(0, @a.w, 0, @a.h, x, y)
                  @a
          end
  
      # ...

These two methods make up the public face of the solver. First, initialize()
creates an instance of the Array2D we just examined. Don't let the bit shifting
throw you, 1 << n is just another way to say the 2**n from the quiz.

The other method, tile_with_empty(), takes an x and y position for the empty
square and kicks-off the process to find a solution. We can see that it sets
some kind of counter, calls tile_recursive() with the dimensions of the board
and the empty square coordinates, then returns the modified Array2D. Clearly we
need to see tile_recursive() to make sense of this:

      # ...
      
          private

          # tiles the area of @a determined by xb,xe and yb,ye (b is begin,
          # e is end, so xb,xe is like the range xb...xe) with trominos,
          # leaving empty_x,empty_y empty
          def tile_recursive(xb, xe, yb, ye, empty_x, empty_y)
                  half=(xe-xb)/2
                  if half==1
                          # easy, just one tromino
                          @tilenum+=1
                          # set all 4 squares, empty is fixed below
                          @a[xb , yb ]=@tilenum
                          @a[xb+1, yb ]=@tilenum
                          @a[xb , yb+1]=@tilenum
                          @a[xb+1, yb+1]=@tilenum
                  else
                          # tile recursive
                          mytile=(@tilenum+=1)
                          # where to split the ranges
                          xh, yh=xb+half, yb+half
                          [ # the 4 sub parts:
                          [xb, xh, yb, yh, xh-1, yh-1],
                          [xh, xe, yb, yh, xh , yh-1],
                          [xb, xh, yh, ye, xh-1, yh ],
                          [xh, xe, yh, ye, xh , yh ]
                          ].each { |args|
                                  # if empty_x,empty_y is in this part, we
                                  # have to adjust the last two arguments
                                  if (args[0]...args[1]).member?(empty_x) &&
                                     (args[2]...args[3]).member?(empty_y)
                                          args[4]=empty_x
                                          args[5]=empty_y
                                  end
                                  tile_recursive(*args)
                                  @a[args[4], args[5]]=mytile
                          }

                  end
                  # fix empty square
                  @a[empty_x, empty_y]=nil
          end
  end
  
  # ...

This is the heart of Dominik's solution. The method is a code representation of
the proof I outlined earlier. It starts by locating the center of the board.
It can branch two different ways from there. The first is when the center is 1,
showing that we're dealing with the trivial n = 1 case from the proof. When
that's the case, the code fills the board with the counter we saw initialized in
the previous method. That will actually fill four squares instead of the needed
three, but if you glance at the last line of the program you'll see that the
empty square is cleared before we return.

The other case the method deals with (the else clause), is for all bigger values
of n. Here, the method memorizes its counter, because it will be increased by
the coming recursion. Then, using the earlier found half-way point, the board
is divided into four equal quadrants. When created, the corner at the center of
the board is used as the new empty square, which represents our "well placed
center L-tromino" from the quiz. The code then corrects that auto-generated
empty square for the quadrant that already had one. Finally, the method
recurses by passing the dimensions of the new quadrant and the empty square for
that quadrant. After the quadrant is filled by that call, the corners are set
to the counter value memorized earlier.

That method will fill the entire board with numbered L-trominos by the time it
returns. The application code needed to finish off the solution is minimal:

  # ...
  
  if $0 == __FILE__
          n=(ARGV[0] || 3).to_i
          d=1 << n
          maxw=((d*d-1)/3).to_s.size
          tiler=TrominoTiler.new(n)
          # show solutions for all possible empty squares
          d.times { |y|
                  d.times { |x|
                          puts tiler.tile_with_empty(x, y).to_s { |v|
                                  v.to_s.rjust(maxw)
                          }, ""
                  }
          }
  end

Dominik reads the specified size or defaults, then uses two lines of clever math
to determine what the largest numbered L-tromino is going to be in the result.
The code memorizes the size of that number, for use in the output block.

Then we get to the solver. An instance of TrominoTiler is created and
tile_with_empty() is called not for one random square by for all possible empty
squares. Those results are shown using Array2D's to_s() method. The passed
block pads small numbers to the maximum width memorized earlier. That gives us
a complete solution.

Many thanks to all you clever tilers out there. Golomb would be proud.

Ruby Quiz for next week: Send in some fresh suggestions. We're out of topics
and need some new material. Ruby Quiz will take a one week break to give you
all time to find some and me time to participate in one of the codefests. Enjoy
the break, but don't forget to send in those ideas!

I am a bit late with my solution, but it's my first Ruby Quiz, and my
largest ruby program so far, so I'll send it in anyway... I didn't know
the problem, but found the same solution everybody else did. I have not
used recursion in the solution, in order to not compute the low-level
solutions over and over again. Instead I compute the 'overlays'
(partial solutions) from the smallest to the largest, then I rotate
them properly, and finally put them all on the board.

I have some requests, if anyone would be so kind as to take a look:

1) Style in general. What have I done that is 'unrubyish'? Are there
more elegant ways of doing things?

2) If a want a 'mathematical' vector, that treats + as addition and not
as concatenation, what can I do? I added the method 'add!' to class
Array for this, but it would be nice to know if there is a standard
facility for this.

3) I gave my Board class a copy method. Is there a better way to copy
an array than concatenating the source to an empty ([]) target? I found
no 'copy' method in the docs.

The code:

···

===============================================

class Array
  def add!(other)
    if other.length != length
      somethingswrong
    end
    each_index { |i| self[i] += other[i] }
    self
  end
end

# Represents the board. Handles rotation and board manipulation.
class Board

  attr_reader :n, :squares
  attr_writer :n, :squares

  # The board size will be 2**r by 2**r.
  def initialize(r = 0, val = 0)
    @n = 2**r
    @squares = Array.new(@n**2, val)
  end

  # Make a deep copy
  def copy
    retval = Board.new
    retval.n = @n
    retval.squares = []
    retval.squares.concat(@squares)
    retval
  end

  # Prettyprinting the board
  def to_s
    output = "Board is #{@n} by #{@n}.\n"
    @n.times do |i|
      @squares[@n*i...@n*(i+1)].each do |s|
        output += sprintf("%5d ", s)
      end
      output += "\n"
    end
    output += "\n"
  end

  # Rotate the board by 90 degrees, CCW.
  def rotate!
    rotated = Array.new(@n**2, 0)
    @n.times do |i|
      @n.times do |j|
        rotated[@n*i + j] = @squares[@n*j + (@n-i-1)]
      end
    end
    @squares = rotated
    self
  end

  def increment!(val = 1)
    @squares.each_index { |i| @squares[i] += val if @squares[i] > 0 }
    self
  end

  # Set a square to a particular value
  def set!(row, col, val)
    @squares[@n*row + col] = val
    self
  end

  # Overlay a sub-board over this one, at a specific location.
  # The (row, col) coordinates should be the upper left corner
  # of the area to overlay. Overlaying means we simply add the
  # values of the inserted board to the current board values.
  # We do not check that the insertion is valid wrt size of
  # sub-board, position, etc.! So be careful...
  def insert!(row, col, subboard)
    sn = subboard.n
    row.upto(row + sn - 1) do |r|
      rowtoadd = subboard.squares[(r-row)*sn...(r-row+1)*sn]
      target = @squares[(r*@n + col)...(r*@n + col + sn)]
# puts "----" + target.to_s
# puts "++++" + rowtoadd.to_s
      target.add!(rowtoadd)
      @squares[(r*@n + col)...(r*@n + col + sn)] = target
    end
    self
  end

end

class TilingPuzzle

  def initialize(r)
    @board = Board.new(r)
    @r = r
  end

  def to_s
    @board.to_s
  end

  def solve!(row, col)
    # Make some overlays of increasing size
    overlays = []
    # Initialize the first overlays
    overlays[0] = Board.new(0, 0)
    overlays[1] = Board.new(1, 1)
    overlays[1].set!(0, 0, 0)
    # Now build every successive overlay
    2.upto(@r) do |i|
      # Every overlay consists of four copies of the previous one,
      # incremented by the number of L-tiles in it every time.
      overlays[i] = Board.new(i)
      o = overlays[i-1].copy
      inc = 4**(i-2)
      pl = 2**(i-1)
      o.increment!(inc)
      overlays[i].insert!(pl/2, pl/2, o)
      o.increment!(inc)
      overlays[i].insert!(pl, pl, o)
      o.rotate!.increment!(inc)
      overlays[i].insert!(0, pl, o)
      o.rotate!.rotate!.increment!(inc)
      overlays[i].insert!(pl, 0, o)
    end

    # Now we can simply tile every overlay around the empty spot,
    # as long as we rotate them properly. Let's first compute the
number
    # of rotations necessary
    rots = [0]
    @r.downto(1) do |i|
      # Can I make this more elegant?
      if (row >= 2**(i-1)) && (col < 2**(i-1))
        rots[i] = 1;
      elsif (row >= 2**(i-1)) && (col >= 2**(i-1))
        rots[i] = 2;
      elsif (row < 2**(i-1)) && (col >= 2**(i-1))
        rots[i] = 3
      else
        rots[i] = 0
      end
# puts "row = #{row}, col = #{col}"
      row = row % (2**(i-1))
      col = col % (2**(i-1))
    end

    # Now, let's put everything in place!
    offsetrow, offsetcol = 0, 0
    @r.downto(1) do |i|
      (rots[i]).times { overlays[i].rotate! }
      @board.insert!(offsetrow, offsetcol, overlays[i])
      offsetrow += 2**(i-1) if [1, 2].include?(rots[i])
      offsetcol += 2**(i-1) if [2, 3].include?(rots[i])
    end
    self
  end

end

size = 4
row = rand(2**size)
col = rand(2**size)
puts TilingPuzzle.new(size).solve!(row, col)

====================================================

Atgeirr Flø Rasmussen

1) Style in general. What have I done that is 'unrubyish'? Are there
more elegant ways of doing things?

2) If a want a 'mathematical' vector, that treats + as addition and not
as concatenation, what can I do? I added the method 'add!' to class
Array for this, but it would be nice to know if there is a standard
facility for this.

3) I gave my Board class a copy method. Is there a better way to copy
an array than concatenating the source to an empty () target? I found
no 'copy' method in the docs.

I have some ideas about the first 2 but I'll leave it to the experts to
give you the 'good' answers. But for 3, some_copy = some_array.dup
should do the trick.

i think it's often better to implement your own copy method since dup only
copies the arrray - not it's contents.

   harp:~ > irb

   irb(main):001:0> a = [ hash = {:k => :v} ]
   => [{:k=>:v}]
   irb(main):002:0> a_dup = a.dup
   => [{:k=>:v}]
   irb(main):003:0> a[0]['key'] = 'value'
   => "value"
   irb(main):004:0> a_dup
   => [{:k=>:v, "key"=>"value"}]

clone doesn't really help either

   irb(main):005:0> a = [ hash = {:k => :v} ]
   => [{:k=>:v}]
   irb(main):006:0> a_clone = a.clone
   => [{:k=>:v}]
   irb(main):007:0> a[0]['key'] = 'value'
   => "value"
   irb(main):008:0> a_clone
   => [{:k=>:v, "key"=>"value"}]

my utility classes never get written w/o

   def copy obj
     Marshal::load(Marshal::dump(obj))
   end

although i know this is frowned upon...

FYI.

-a

···

On Fri, 27 May 2005, Greg Brown wrote:

1) Style in general. What have I done that is 'unrubyish'? Are there
more elegant ways of doing things?

2) If a want a 'mathematical' vector, that treats + as addition and not
as concatenation, what can I do? I added the method 'add!' to class
Array for this, but it would be nice to know if there is a standard
facility for this.

3) I gave my Board class a copy method. Is there a better way to copy
an array than concatenating the source to an empty () target? I found
no 'copy' method in the docs.

I have some ideas about the first 2 but I'll leave it to the experts to
give you the 'good' answers. But for 3, some_copy = some_array.dup
should do the trick.

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
My religion is very simple. My religion is kindness.
--Tenzin Gyatso

===============================================================================

As far as I can understand, dup would actually work here, since the
Array contains only numbers. Is that correct? Anyway, I did not know
about dup, so thanks! I do not really understand the difference between
dup and clone, though.

Atgeirr

As far as I can understand, dup would actually work here, since the
Array contains only numbers. Is that correct? Anyway, I did not know
about dup, so thanks! I do not really understand the difference between
dup and clone, though.

#clone works like #dup, but it keeps more properties of the original object:

#clone keeps a frozen object frozen, while #dup doesn't:

irb(main):007:0> a="hallo"
=> "hallo"
irb(main):008:0> a.freeze
=> "hallo"
irb(main):009:0> a.frozen?
=> true
irb(main):010:0> a.dup.frozen?
=> false
irb(main):011:0> a.clone.frozen?
=> true

and a cloned singleton class can not be instantieted, while a dupped singleton class can:

irb(main):012:0> (class << a; self; end).new
TypeError: can't create instance of virtual class
         from (irb):12:in `new'
         from (irb):12
irb(main):013:0> (class << a; self; end).clone.new
TypeError: can't create instance of virtual class
         from (irb):13:in `new'
         from (irb):13
irb(main):014:0> (class << a; self; end).dup.new
=> ""

For the full details, here is the source of both functions from ruby-1.8.2/object.c:

VALUE
rb_obj_clone(obj)
     VALUE obj;
{
     VALUE clone;

     if (rb_special_const_p(obj)) {
         rb_raise(rb_eTypeError, "can't clone %s", rb_obj_classname(obj));
     }
     clone = rb_obj_alloc(rb_obj_class(obj));
     RBASIC(clone)->klass = rb_singleton_class_clone(obj);
     RBASIC(clone)->flags = (RBASIC(obj)->flags | FL_TEST(clone, FL_TAINT)) & ~(FL_FREEZE|FL_FINALIZE);
     init_copy(clone, obj);
     RBASIC(clone)->flags |= RBASIC(obj)->flags & FL_FREEZE;

     return clone;
}

VALUE
rb_obj_dup(obj)
     VALUE obj;
{
     VALUE dup;

     if (rb_special_const_p(obj)) {
         rb_raise(rb_eTypeError, "can't dup %s", rb_obj_classname(obj));
     }
     dup = rb_obj_alloc(rb_obj_class(obj));
     init_copy(dup, obj);

     return dup;
}

Dominik

···

On Mon, 30 May 2005 13:30:21 +0200, atgeirr <atgeirr@math.uio.no> wrote:
         from :0

Yes, in instances where you're just dealing with simple numbers, dup() should meet your needs.

James Edward Gray II

···

On May 30, 2005, at 6:30 AM, atgeirr wrote:

As far as I can understand, dup would actually work here, since the
Array contains only numbers. Is that correct?