[SUMMARY] Tiling Turmoil (#33)

R

Ruby Quiz

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!
 
A

atgeirr

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 += other }
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 += val if @squares > 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 = Board.new(i)
o = overlays[i-1].copy
inc = 4**(i-2)
pl = 2**(i-1)
o.increment!(inc)
overlays.insert!(pl/2, pl/2, o)
o.increment!(inc)
overlays.insert!(pl, pl, o)
o.rotate!.increment!(inc)
overlays.insert!(0, pl, o)
o.rotate!.rotate!.increment!(inc)
overlays.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 = 1;
elsif (row >= 2**(i-1)) && (col >= 2**(i-1))
rots = 2;
elsif (row < 2**(i-1)) && (col >= 2**(i-1))
rots = 3
else
rots = 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).times { overlays.rotate! }
@board.insert!(offsetrow, offsetcol, overlays)
offsetrow += 2**(i-1) if [1, 2].include?(rots)
offsetcol += 2**(i-1) if [2, 3].include?(rots)
end
self
end

end


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

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

Atgeirr Flø Rasmussen
 
G

Greg Brown

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.
 
A

Ara.T.Howard

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
--
===============================================================================
| 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
===============================================================================
 
A

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.

Atgeirr
 
D

Dominik Bathon

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
from :0
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
from :0
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
 
J

James Edward Gray II

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

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

James Edward Gray II
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,772
Messages
2,569,593
Members
45,108
Latest member
AlbertEste
Top