[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

[QUIZ] Tiling Turmoil (#33

James Gray

5/20/2005 12:38:00 PM

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rub...

3. Enjoy!

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Greg Brown

This weeks quiz is based off of a mathematical proof we were taught in my
discrete mathematics class a few weeks ago. Though I can already hear some of
you running for the hills at the mention of the four letter word that is math, I
can assure you that this is more of a thinking problem than it is number
crunching. The basic idea is pretty simple.

You're going to tile L-trominos on a 2**n by 2**n board that is missing a single
square. What's an L-tromino? It's simply a 2 by 2 square with a corner missing
that looks like an L.

For further clarification, this is what they look like:

#
# #

Well, I guess it's time to stop being vague and give you the problem definition.

For any 2**n by 2**n board with a single square missing in a random location,
you must write a program that will tile L-trominos in such a way that the grid
is completely filled in.

Your program should take the value for n as the input, and somehow display the
board with the trominos properly laid out as the output. It should place the
missing square at random each time the board is generated. (Otherwise this would
be too easy!)

For example, the empty board of n = 2 would be something like this:

_ _ _ _
_ _ X _
_ _ _ _
_ _ _ _

The solution for that might look like:

1 1 2 2
1 5 X 2
3 5 5 4
3 3 4 4

As you can see, all squares are completely filled with the exception of the
empty square which was randomly placed.

It may look simple on a 4 by 4 board, but once you get up to a 128 by 128 or
even 4096 by 4096, it becomes more and more obvious that guess and check is just
not going to work.

The level of difficulty of this problem depends entirely on how much you already
know about it. If you want an easy ride, look for the proof and just implement
it.

If you want more of a challenge, avoid Googling the topic and try to find clever
ways to actually show how your program solves the problem.

Hope you have fun with this quiz, and if you write a really good one, you can
help me tile my bathroom floor next week as a prize. :)


7 Answers

Kero van Gelder

5/22/2005 1:38:00 PM

0

> For any 2**n by 2**n board with a single square missing in a random location,
> you must write a program that will tile L-trominos in such a way that the grid
> is completely filled in.
>
> Your program should take the value for n as the input, and somehow display the
> board with the trominos properly laid out as the output. It should place the
> missing square at random each time the board is generated. (Otherwise this would
> be too easy!)
[snip]
> The level of difficulty of this problem depends entirely on how much you already
> know about it. If you want an easy ride, look for the proof and just implement
> it.

Yeah. I knew the problem, I didn't need google.

So I decided to
- generate* all (sub)tiles and
- have only that part of the board in memory that needs to be printed
(quadtree into details for current line, by using the Ruby stack;
If anyone knows how to keep less administration, let me know :)

> Hope you have fun with this quiz, and if you write a really good one, you can
> help me tile my bathroom floor next week as a prize. :)

Not a chance :)

Besides I fear my code is more cryptic than it could be.

Bye,
Kero.

+--- Kero ------------------------- kero@chello@nl ---+
| all the meaningless and empty words I spoke |
| Promises -- The Cranberries |
+--- M38c --- http://members.chello.nl/k... ---+


# 1) There is no regular coverage of trominos over a 2**n square board (3 does
# not divide 2**n)
# 2) however, tromino's can cover 3/4 of such a board. the factor three has
# been introduced. Which three quarters? Let me show you:
#
# +---+ Which in itself is a tromino-shaped area, but twice as big.
# | | You can't miss the factor 2 which also appears in 2**n, so
# | +-+ this basically gives the algorithm for the solution away.
# | | |
# +-+ +-+-+ 3) We shall display the board. I'm going to try to use the
# | | | | recursion efficiently and not keep the entire board in
# | +---+ | memory.
# | | |
# +---+---+ 4) Line-by-line we see one or two squares of a tromino that we
# must know. Three squares, four orientations, twelve ids.

# F T L J, clockwise numbered :F1 :F2 :F3 etc
# We would have, from the above shape
# :L => [
# [ :F2, :F3, 0, 0 ],
# [ :F1, :L3, 0, 0 ],
# [ :L3, :L2, :L1, :J1 ],
# [ :L2, :L1, :J3, :J2 ]
# ],
# but that's not recursive, so we get (clockwise):
# :L1 => [:L1, :J1, :J2, :J3],
# :L2 => [:L3, :L2, :L1, :L2],
# :L3 => [:F2, :F3, :L3, :F1],
# We're lazy (and we hate typos) so we'll generate this

class Array
# shift element and append it again, name from CPU registers
def rotate!()
push(shift)
end
# substitute first occurrence of +orig+
def sub(orig, replace)
result = dup
result[index(orig)] = replace
result
end
end

Trominos = [ :J0, :T0, :F0, :L0 ] # rotating J counterclockwise gives T, F, L
Sub = {}

# Set tromino in 2x2 square, clockwise, using :X0 as 'empty'
# With only these set, the script already works for n==1 :)
a = (0..3).to_a # clockwise numbering of :J, 0 being 'empty' (excluded square)
Trominos.each { |sym|
str = (0..3).collect { |i| ":#{sym}#{a[i]}".sub(/0/, "") }.join(", ")
Sub[sym] = eval("[#{str}]")
a.rotate! # rotate counterclockwise
}

# For all 12 possibilities, set subsquare, clockwise
(0..3).each { |i|
counter = Trominos[(i+1) % 4]
sym = Trominos[i]
clockwise = Trominos[(i+3) % 4]
first = eval(":#{sym}".sub(/0/, "1"))
Sub[first] = Sub[counter].sub(counter, first)
second = eval(":#{sym}".sub(/0/, "2"))
Sub[second] = Sub[sym].sub(sym, second)
third = eval(":#{sym}".sub(/0/, "3"))
Sub[third] = Sub[clockwise].sub(clockwise, third)
}

def base(n, x, y)
case [x>>(n-1), y>>(n-1)]
when [0, 0]; Sub[:J0]
when [1, 0]; Sub[:L0]
when [1, 1]; Sub[:F0]
when [0, 1]; Sub[:T0]
end
end

def solve(n, x, y, *fields)
if n == 1
puts fields.join(" ").sub(/.0/, " ")
else
n = n - 1
nn = 2 ** n
x, y = x % nn, y % nn
subs = fields.collect { |f|
# subsquares can be looked up, for :X0 we need proper tromino
Trominos.include?(f) ? base(n, x, y) : Sub[f]
}
solve(n, x, y, *subs.collect { |s0, s1, s2, s3| [s0, s1] }.flatten)
solve(n, x, y, *subs.collect { |s0, s1, s2, s3| [s3, s2] }.flatten)
end
end

if ARGV[0].to_i == 0
STDERR.puts "Usage: #{$0} n # where the field is 2**n square"
else
n = ARGV[0].to_i
size = 2 ** n
x, y = rand(size), rand(size)
puts "Hole at (#{x}, #{y}) # note that (0, 0) is top left"
b = base(n, x, y)
solve(n, x, y, *b.values_at(0, 1))
solve(n, x, y, *b.values_at(3, 2))
end


Dominik Bathon

5/22/2005 10:42:00 PM

0

Hallo,

here is my solution. I didn't know this problem and I didn't google ;-)
It works recursive:

For n=1 it is easy: just place one tromino, so that the empty square is
empty.

For bigger n, divide the problem into four n-1 sub problems. For example:

________
________
_____x__
________
________
________
________
________

This can be solved by solving the following problems:

____
____
____
___o

____
____
_x__
____

___o
____
____
____

o___
____
____
____

Putting their solutions together we get:

11112222
11112222
11112x22
111o2222
333oo444
33334444
33334444
33334444

So the last missing tromino can easily be placed.

The program accepts one optional argument (n), it defaults to 3.
It outputs solutions for all possible empty squares.

Dominik


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

# 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


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

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


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


Matthew Westcott

5/22/2005 11:15:00 PM

0

Here's my solution (and my first attempt at a Ruby Quiz) - also
completed without googling.

The distinguishing feature here compared to the other solutions posted
so far, is that I'm doing the recursion by creating a tree of Square
objects beforehand, in an effort to make it more OOPish.



#!/usr/bin/ruby

# Proof by induction
# Base case: a 2x2 board with one missing square.
# Trivially, the remaining 3 squares form an L-tromino.
# Step:
# Assume that any (2**(n-1) x 2**(n-1)) board with a square missing
can be solved.
# A (2**n x 2**n) board can be divided into four (2**(n-1) x 2**(n-1))
quadrants.
# One of these quadrants contains the missing square.
# An L-tromino can be placed in the centre of the board, rotated such
that it occupies
# one square of each of the other three quadrants.
# The result of this is four quadrants, each of which has one square
missing.
# By our original assumption, each of these can be solved.

# Note that at each stage of subdivision, each quadrant contains
precisely one
# square that is either 1) the missing square, or 2) occupied by an
L-tromino
# that overlaps onto other quadrants.

class Square
# Represents a square portion of the board.

attr_reader :central_corner_x, :central_corner_y

def initialize(size, left = 0, top = 0, central_corner_x = nil,
central_corner_y = nil)
@size = size # Size of this square
@left = left # X coordinate of leftmost point
@top = top # Y coordinate of topmost point
@central_corner_x = central_corner_x
@central_corner_y = central_corner_y
# Coordinates of the corner closest to the middle
# of the parent square (or nil if the square has no parent)

if size > 1
# divide into quadrants
quad_size = @size / 2
@quadrants = [
Square.new(quad_size, @left, @top, @left + quad_size - 1, @top +
quad_size - 1),
Square.new(quad_size, @left + quad_size, @top, @left + quad_size,
@top + quad_size - 1),
Square.new(quad_size, @left, @top + quad_size, @left + quad_size -
1, @top + quad_size),
Square.new(quad_size, @left + quad_size, @top + quad_size, @left +
quad_size, @top + quad_size)
]
end
end

def contains?(x, y)
# Determine whether this square contains the given point
(@left...(@left+@size)) === x && (@top...(@top+@size)) === y
end

def solve(board, missing_x, missing_y, count = 1)
# board = a board which is to have the portion indicated by this
Square object filled with L-trominos
# missing_x, missing_y - the coordinates of a square not to be filled
# count = the current L-tromino number
# Returns the next available unused L-tromino number
if @size == 1
board[@top][@left] = count unless contains?(missing_x, missing_y)
count
else
next_count = count + 1
@quadrants.each { |quadrant|
if quadrant.contains?(missing_x, missing_y)
# a square in this quadrant is already missing - can solve the
quadrant straight off
next_count = quadrant.solve(board, missing_x, missing_y, next_count)
else
# artificially 'create' a missing square before solving the quadrant
board[quadrant.central_corner_y][quadrant.central_corner_x] = count
next_count = quadrant.solve(board, quadrant.central_corner_x,
quadrant.central_corner_y, next_count)
end
}
next_count
end
end
end

puts "Board magnitude? (1 = 2x2, 2 = 4x4, 3 = 8x8, 4 = 16x16...)"
n = gets.to_i
size = 2**n
digits = (n*2) / 5 + 1 # how many base-32 digits we need to give each
L-tromino its own ID

board = (0...size).collect{ (0...size).collect { 0 } }
Square.new(size).solve(board, rand(size), rand(size))

board.each do |row|
puts row.map{ |i| i.to_s(32).rjust(digits) }.join(' ')
end

Jannis Harder

5/23/2005 7:40:00 PM

0


Jim Freeze

5/24/2005 11:26:00 PM

0

* Pedro <pedro.wotan@mundo-r.com> [2005-05-25 06:33:24 +0900]:

> Here's my solution, and without googling!.
>
> I'm new in Ruby, and I have a little doubt in my solution. The to_s
> method is too slow in the way I implemented it, Is there any better
> approach?.
>

Yes. There are a couple of things you can do.

> def to_s
> cad = ''
> @tiles.each_index { |i|
># cad += @tiles[i] + ' '
cad << @tiles[i] << ' '
># cad += "\n" if i%@w==@w-1
cad << "\n" if i%@w==@w-1
> }
> return cad
> end

The other thing you can do (with some extra modification) is start with:

cad = []

and when you are done, do
cad.join("\n")


--
Jim Freeze
Ruby: I can explain it to ya but I can't understand it fer ya.


James Gray

5/25/2005 5:04:00 PM

0

On May 24, 2005, at 4:33 PM, Pedro wrote:

> Here's my solution, and without googling!.

Nice job.

> I'm new in Ruby,

Welcome!

> and I have a little doubt in my solution. The to_s method is too
> slow in the way I implemented it, Is there any better approach?.

[snip]

> def to_s
> cad = ''
> @tiles.each_index { |i|
> cad += @tiles[i] + ' '
> cad += "\n" if i%@w==@w-1
> }
> return cad
> end

I haven't tested or timed it, but one way might be:

def to_s
@tiles.join(" ").sub!(/((?:[0-9X]+\s+){#{@w}})/, "\\1\n") + "\n"
end

Hope that helps.

James Edward Gray II



Bill Atkins

5/25/2005 6:43:00 PM

0

Here's my solution. It defines a class called Square to represent the
board. Most of the work is done in the recursive #do_tile function.

The algorithm works by breaking down a board into four quadrants and
determining which of these quadrants has a non-empty square. It then
places a tile so that the tile will bracket the edge of the quadrant
with the non-empty square. It then recursively tiles these quadrants.
Each of these quadrants will have an empty space because the tile
placed in the last step has one square in each of the three quadrants
that didn't already have a non-empty square.

I didn't google, but I did have this problem in my Discrete Structures
class earlier in the year. :P

class Square
def initialize n
raise "n must be > 0" unless n > 0

# create a 2**n x 2**n board filled with dashes
@size = 2 ** n
@board = Array.new @size do
Array.new @size do
"-"
end
end

# a randomly-placed X indicates the hole
@board[ rand(@size) ][ rand(@size) ] = "X"
@cur_tile = 1
end

def to_s
@board.collect { |row| row.collect { |item|
"%#{Math.log(@size).to_i}s" % [item] }.join " " }.join "\n"
end

def tile!
# (0, 0, @size) means the @size x @size square extending to the right and
# downward from (0, 0)
do_tile 0, 0, @size
end

def do_tile row, col, size
# base case
if size < 2
return
end

sub_size = size / 2

# define each quadrant
top_left = [row, col, sub_size]
top_right = [row, col + sub_size, sub_size]
bottom_left = [row + sub_size, col, sub_size]
bottom_right = [row + sub_size, col + sub_size, sub_size]

# one of the quadrants will have a non-empty tile; bracket that quadrant
# with a tile
if has_filled_tile? *top_left
@board[row + sub_size - 1] [col + sub_size] = @cur_tile
@board[row + sub_size] [col + sub_size] = @cur_tile
@board[row + sub_size] [col + sub_size - 1] = @cur_tile
elsif has_filled_tile? *top_right
@board[row + sub_size - 1] [col + sub_size - 1] = @cur_tile
@board[row + sub_size] [col + sub_size - 1] = @cur_tile
@board[row + sub_size] [col + sub_size] = @cur_tile
elsif has_filled_tile? *bottom_left
@board[row + sub_size - 1] [col + sub_size - 1] = @cur_tile
@board[row + sub_size - 1] [col + sub_size] = @cur_tile
@board[row + sub_size] [col + sub_size] = @cur_tile
elsif has_filled_tile? *bottom_right
@board[row + sub_size - 1] [col + sub_size - 1] = @cur_tile
@board[row + sub_size] [col + sub_size - 1] = @cur_tile
@board[row + sub_size - 1] [col + sub_size] = @cur_tile
else
raise "broken"
end

@cur_tile += 1

# recursively tile the quadrants
do_tile *top_left
do_tile *top_right
do_tile *bottom_left
do_tile *bottom_right
end
private :do_tile

def has_filled_tile? row, col, size
size.times do |r|
size.times do |c|
if @board[row + r] [col + c] != "-"
return true
end
end
end
false
end
end

s = Square.new 2
s.tile!
puts s.to_s

On 5/20/05, Ruby Quiz <james@grayproductions.net> wrote:
> The three rules of Ruby Quiz:
>
> 1. Please do not post any solutions or spoiler discussion for this quiz until
> 48 hours have passed from the time on this message.
>
> 2. Support Ruby Quiz by submitting ideas as often as you can:
>
> http://www.rub...
>
> 3. Enjoy!
>
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
>
> by Greg Brown
>
> This weeks quiz is based off of a mathematical proof we were taught in my
> discrete mathematics class a few weeks ago. Though I can already hear some of
> you running for the hills at the mention of the four letter word that is math, I
> can assure you that this is more of a thinking problem than it is number
> crunching. The basic idea is pretty simple.
>
> You're going to tile L-trominos on a 2**n by 2**n board that is missing a single
> square. What's an L-tromino? It's simply a 2 by 2 square with a corner missing
> that looks like an L.
>
> For further clarification, this is what they look like:
>
> #
> # #
>
> Well, I guess it's time to stop being vague and give you the problem definition.
>
> For any 2**n by 2**n board with a single square missing in a random location,
> you must write a program that will tile L-trominos in such a way that the grid
> is completely filled in.
>
> Your program should take the value for n as the input, and somehow display the
> board with the trominos properly laid out as the output. It should place the
> missing square at random each time the board is generated. (Otherwise this would
> be too easy!)
>
> For example, the empty board of n = 2 would be something like this:
>
> _ _ _ _
> _ _ X _
> _ _ _ _
> _ _ _ _
>
> The solution for that might look like:
>
> 1 1 2 2
> 1 5 X 2
> 3 5 5 4
> 3 3 4 4
>
> As you can see, all squares are completely filled with the exception of the
> empty square which was randomly placed.
>
> It may look simple on a 4 by 4 board, but once you get up to a 128 by 128 or
> even 4096 by 4096, it becomes more and more obvious that guess and check is just
> not going to work.
>
> The level of difficulty of this problem depends entirely on how much you already
> know about it. If you want an easy ride, look for the proof and just implement
> it.
>
> If you want more of a challenge, avoid Googling the topic and try to find clever
> ways to actually show how your program solves the problem.
>
> Hope you have fun with this quiz, and if you write a really good one, you can
> help me tile my bathroom floor next week as a prize. :)
>
>



--
Bill Atkins