[/QUIZ] #63: Grid Folding

L

Luke Blanshard

--------------090508050402060304030007
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Greetings all,

Attached is my solution. It uses nested arrays to simulate the 3-d
grid cells, and adds methods to Array to effect this. It handles
arbitrary rectangles of 2**n by 2**m. The unit tests provided earlier pass.

I can't help feeling there should be a direct numerical way to
calculate this sequence. To study the sequence, I wrote a second
script, also attached, that prints the bits of the resulting sequence
from any given rectangle dimensions and list of folds. (I subtract one
from the sequence to make it zero-based.) However, even with a fair
amount of studying lists of bit patterns I haven't cracked the code.

Luke Blanshard


--------------090508050402060304030007
Content-Type: text/plain;
name="fold.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="fold.rb"

#!/usr/bin/ruby -w
#
# Ruby Quiz #63, Grid Folding

require "strscan"

# Creates the grid, applies the folds to it
def fold( v, h, folds )
grid = Array.new(v){|i|Array.new(h){|j|[j*v+i+1]}}
s, c = StringScanner.new(folds), ""
grid.send("fold_"+c+"!") while c=s.getch
raise "Too few folds" if grid.size != 1 or grid[0].size != 1
grid[0][0]
end

class Array
# Slices self in half, yields removed and retained elements
def fold!(forward)
raise "Can't fold odd-sized array" if size[0] == 1
start = if forward then 0 else size/2 end
a = slice! start, size/2
zip(a.reverse!){|e|yield e[1], e[0]}
end

# Vertical fold, top to bottom or vice versa
def fold_v!(down)
each{|c|c.fold!(down){|a,b|b.unshift(*a.reverse!)}}
end

# Horizontal fold, left to right or vice versa
def fold_h!(left)
fold!(left){|a,b|a.each_index{|i|b.unshift(*a.reverse!)}}
end

def fold_T!; fold_v! true; end
def fold_B!; fold_v! false; end
def fold_L!; fold_h! true; end
def fold_R!; fold_h! false; end
end

# Parses ARGV, returns v, h, folds
def get_args
def usage
puts "Usage: #{File.basename($0)} [<size>] <folds>\n"+
" where <size> is a power of 2 or a pair thereof separated by 'x', like 4x8\n"+
" and <folds> is a string of fold directions from T, L, R, B\n"
exit
end
usage unless (1..2) === ARGV.size
size, folds = [16, 16], ARGV[-1]
usage unless folds =~ /^[TLRB]+$/
if ARGV.size == 2
size = ARGV[0].split('x').map{|s|s.to_i}
usage unless (1..2) === size.size
size = Array.new(2, size[0]) if size.size == 1
size.each{|i|raise "%d not a power of 2"%i unless i>0 and i&(i-1)==0}
end
v, h = *size
return v, h, folds
end

# Main program
if $0 == __FILE__
p fold( *get_args )
end

--------------090508050402060304030007
Content-Type: text/plain;
name="foldbits.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="foldbits.rb"

#!/usr/bin/ruby -w

require 'fold.rb'


class Integer
def bits
return 0 if self <= 1
return 1+(self/2).bits
end
end

if $0 == __FILE__
v, h, folds = get_args
bits = v.bits + h.bits
puts fold(v, h, folds).map{|i|sprintf "%0*b", bits, i-1}
end

--------------090508050402060304030007--
 
M

Michael Burrows

------=_NextPart_000_0026_01C61F60.9D2384F0
Content-Type: text/plain;
charset="us-ascii"
Content-Transfer-Encoding: 7bit


And here's mine. I didn't use a grid at all - working it out a cell at a
time.

One key difference is that mine works out the dimensions from the supplied
folds. It checks that the grid to be square (raises an exception
otherwise), but only to pass what might have been an over-zealous test case.

Regards,
Mike


-----Original Message-----
From: Luke Blanshard [mailto:[email protected]]
Sent: 22 January 2006 13:56
To: ruby-talk ML
Subject: [/QUIZ] #63: Grid Folding

Greetings all,

Attached is my solution. It uses nested arrays to simulate the 3-d
grid cells, and adds methods to Array to effect this. It handles
arbitrary rectangles of 2**n by 2**m. The unit tests provided earlier pass.

I can't help feeling there should be a direct numerical way to
calculate this sequence. To study the sequence, I wrote a second
script, also attached, that prints the bits of the resulting sequence
from any given rectangle dimensions and list of folds. (I subtract one
from the sequence to make it zero-based.) However, even with a fair
amount of studying lists of bit patterns I haven't cracked the code.

Luke Blanshard


------=_NextPart_000_0026_01C61F60.9D2384F0
Content-Type: application/octet-stream;
name="63_grid_folding.rb"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="63_grid_folding.rb"

# Fold 1-dimensionally "forward" - i.e. Left to Right or Top to bottom
class ForwardFolder
def fold1d(position, length, depth, thickness)=0A=
if position >=3D (to_length =3D length / 2)
position -=3D to_length=20
depth +=3D thickness
else
position =3D to_length - position - 1
depth =3D thickness - depth - 1
end
[position, to_length, depth, 2*thickness]
end
end

# Fold 1-dimensionally "backward" - i.e. bottom to top or right to left
class BackwardFolder
def fold1d(position, length, depth, thickness)=0A=
if position >=3D (to_length =3D length / 2)
position =3D length - position - 1
depth =3D thickness - depth - 1
else
depth +=3D thickness
end
[position, to_length, depth, 2*thickness]
end
end

# Mixin to make a Forward or Backward folder a Left or Right folder =
(respectively)
module LeftRightFolder
def fold(x, y, w, h, depth, thickness)=0A=
x, w, depth, thickness =3D fold1d(x, w, depth, thickness)=0A=
[x, y, w, h, depth, thickness]
end
end
=20
# Mixin to make a Forward or Backward folder a Top or Bottom fold =
(respectively)
module TopBottomFolder
def fold(x, y, w, h, depth, thickness)
y, h, depth, thickness =3D fold1d(y, h, depth, thickness)
[x, y, w, h, depth, thickness]
end
end

# The main class
class Fold=0A=
# Map command letters to a suitable folder object
FOLDERS =3D {
'L' =3D> ForwardFolder.new.extend(LeftRightFolder),
'R' =3D> BackwardFolder.new.extend(LeftRightFolder),
'T' =3D> ForwardFolder.new.extend(TopBottomFolder),
'B' =3D> BackwardFolder.new.extend(TopBottomFolder)
}
=09
def self.fold(instructions)
raise("bad instructions") unless instructions.match(/^[LRTB]*$/)

folds =3D instructions.split(//).collect {|i| FOLDERS}
=20
# work out the width and height by "unfolding" by the number of =
Left/Right and Top/Bottom folds respectively
orig_width =3D 1 << folds.grep(LeftRightFolder).length
orig_height =3D 1 << folds.grep(TopBottomFolder).length
=09
# and check that they are the same (i.e. that the grid is square =
(because the spec says so - not that it would break anything)
raise("non-square: unequal width (#{orig_width}) and height =
(#{orig_height}) implied by folds \"#{instructions}\"") unless =
orig_height =3D=3D orig_width
=20
# Do it - a cell at a time...
result =3D []
(0...orig_height).each do |row|
(0...orig_width).each do |col|
=20
n =3D row * orig_width + col + 1 # n being the original cell =
number
x, y =3D col, row
w, h =3D orig_width, orig_height
depth =3D 0
thickness =3D 1
=20
# ...and apply the list of folds
folds.each do |folder|
x, y, w, h, depth, thickness =3D folder.fold(x, y, w, h, depth, =
thickness)
end
=20
result[depth] =3D n # record the layer (or depth) =
that cell n ends up in
end
end
=20
result
end
end

if __FILE__ =3D=3D $0
require 'test/unit'
=0A=
class FoldTest < Test::Unit::TestCase
def test_2x2
folds =3D {"TR" =3D> [4, 2, 1, 3],
"BR" =3D> [2, 4, 3, 1],
"TL" =3D> [3, 1, 2, 4],
"BL" =3D> [1, 3, 4, 2],
"RT" =3D> [1, 2, 4, 3],
"RB" =3D> [3, 4, 2, 1],
"LT" =3D> [2, 1, 3, 4],
"LB" =3D> [4, 3, 1, 2]}
=09
folds.each do |cmds,xpct|
assert_equal xpct, Fold.fold(cmds)
end
end
=09
def test_16x16
xpct =3D [189, 77, 68, 180, 196, 52, 61, 205,
204, 60, 53, 197, 181, 69, 76, 188,
185, 73 , 72, 184, 200, 56, 57, 201,
208, 64, 49, 193, 177, 65, 80, 192,
191, 79, 66, 178, 194, 50, 63, 207,
202, 58, 55, 199, 183, 71, 74, 186,
187, 75, 70, 182, 198, 54, 59, 203,
206, 62, 51, 195, 179, 67, 78, 190,
142, 126, 115, 131, 243, 3, 14, 254,
251, 11, 6, 246, 134, 118, 123, 139,
138, 122, 119, 135, 247, 7, 10, 250,
255, 15, 2, 242, 130, 114, 127, 143,
144, 128, 113, 129, 241, 1, 16, 256,
249, 9, 8, 248, 136, 120, 121, 137,
140, 124, 117, 133, 245, 5, 12, 252,
253, 13, 4, 244, 132, 116, 125, 141,
157, 109, 100, 148, 228, 20, 29, 237,
236, 28, 21, 229, 149, 101, 108, 156,
153, 105, 104, 152, 232, 24, 25, 233,
240, 32, 17, 225, 145, 97, 112, 160,
159, 111, 98, 146, 226, 18, 31, 239,
234, 26, 23, 231, 151, 103, 106, 154,
155, 107, 102, 150, 230, 22, 27, 235,
238, 30, 19, 227, 147, 99, 110, 158,
174, 94, 83, 163, 211, 35, 46, 222,
219, 43, 38, 214, 166, 86, 91, 171,
170, 90, 87, 167, 215, 39, 42, 218,
223, 47, 34, 210, 162, 82, 95, 175,
176, 96, 81, 161, 209, 33, 48, 224,
217, 41, 40, 216, 168, 88, 89, 169,
172, 92, 85, 165, 213, 37, 44, 220,
221, 45, 36, 212, 164, 84, 93, 173]
assert_equal xpct, Fold.fold("TLBLRRTB")
end
=09
def test_invalid
assert_raise(RuntimeError) { Fold.fold("LR") } # too many horz =
folds
assert_raise(RuntimeError) { Fold.fold("TRB") } # too many folds
assert_raise(RuntimeError) { Fold.fold("LR") } # bad input =
dimensions
end
end
end

------=_NextPart_000_0026_01C61F60.9D2384F0--
 
B

Bill.Dolinar

Here's mine. I use arrays within arrays as well. I wrote my own loops
to do the folding. I see now I could have done it much more
succinctly. I went for all the extra credit. Can unfold by noticing
the last element of the array hasn't been folded over and the first
element was previously unfolded. The direction from the first to the
last element gives the fold direction. Then just keep cutting off the
first part of the array.

#! /usr/bin/env ruby -w

=begin
Manages the matrix of values for folding:
[[1], [2],
[3], [4]]

left_fold returns new matrix:
[[1, 2],
[3, 4]]
=end
class FoldMatrix

attr_reader :values

def initialize(values, rows, cols)
@rows = rows
@cols = cols
@values = values
end

# Fold left side of matrix over to right returning new FoldMatrix
def left_fold
fold:)left)
end

# Fold right side of matrix over to left returning new FoldMatrix
def right_fold
fold:)right)
end

# Fold top of matrix down and return new FoldMatrix
def top_fold
fold:)top)
end

# Fold bottom of matrix up and return new FoldMatrix
def bottom_fold
fold:)bottom)
end

# Return the result of folding in flattened array
def result
if (@rows != 1 && @cols != 1)
raise ArgumentError, "Paper not completely folded"
end

@values.flatten
end

private

# Return a matrix element
def array_element(i, j)
@values[i*@cols + j]
end

# Iterate through items in array by folded direction where direction
# is one of :left, :right, :top, :bottom. Iterates going left to
# right then down. Values are already in proper order top to
# bottom.
# Example:
# each_by_fold do |top, bottom|
# new_cell_value = top + bottom
# end
def each_by_fold(fold)
# make sure there are enough rows or columns to fold
case fold
when :left, :right
if @cols <= 1
raise ArgumentError,
"Attemting to fold to #{fold.to_s} with only 1 column"
end
when :top, :bottom
if @rows <= 1
raise ArgumentError,
"Attemting to fold to #{fold.to_s} with only 1 row"
end
end

# setup loop boundaries to loop through unfolded part of page
case fold
when :left
row_start = 0
row_end = @rows - 1
col_start = @cols/2
col_end = @cols - 1
when :right
row_start = 0
row_end = @rows - 1
col_start = 0
col_end = @cols/2 - 1
when :top
row_start = @rows/2
row_end = @rows - 1
col_start = 0
col_end = @cols - 1
when :bottom
row_start = 0
row_end = @rows/2 - 1
col_start = 0
col_end = @cols - 1
end

# loop through row by row reversing items folded to top
row_start.upto(row_end) do |i|
col_start.upto(col_end) do |j|
case fold
when :left, :right
top = array_element(i, @cols - j - 1).reverse
bottom = array_element(i, j)
when :top, :bottom
top = array_element(@rows - i - 1, j).reverse
bottom = array_element(i, j)
end
yield(top, bottom)
end
end
end

# Return a new fold matrix by folding in direction where direction
# is one of :left, :right, :top, :bottom.
def fold(direction)
new_values = []
each_by_fold(direction) do |top, bottom|
new_values << top + bottom
end

case direction
when :left, :right
new_cols = @cols/2
new_rows = @rows
when :top, :bottom
new_cols = @cols
new_rows = @rows/2
end
FoldMatrix.new(new_values, new_rows, new_cols)
end
end

# Determine if a number is a power of 2
def is_power_of_2(number)
return false if number < 1

# keep on shifting left until number equals one (power of 2) or has
# one bit set but isn't one (not power of 2)
while number > 1
number >>= 1
return false if ((number & 1) == 1 && number != 1)
end
true
end

# Get the direction from an unfolded matrix element to the one
# just folded to the top. Both must be in same row or column.
def direction_to(unfolded, folded, rows, cols)
unfolded -= 1
unfolded_i = unfolded / cols
unfolded_j = unfolded % cols

folded -= 1
folded_i = folded / cols
folded_j = folded % cols

case
when unfolded_i == folded_i && unfolded_j < folded_j
:right
when unfolded_i == folded_i && unfolded_j > folded_j
:left
when unfolded_j == folded_j && unfolded_i < folded_i
:bottom
when unfolded_j == folded_j && unfolded_i > folded_i
:top
else
raise ArgumentError, "Values not in same row or column: " +
"#{unfolded}, #{folded}, #{rows}x#{cols}"
end
end

def check_rows_and_cols(rows, cols)
unless is_power_of_2(rows)
raise ArgumentError, "Rows must be power of two"
end
unless is_power_of_2(cols)
raise ArgumentError, "Cols must be power of two"
end
end

# Fold up matrix of numbers using given directions where directions
# are in a string with T = top, B = bottom, L = left, R = right:
# "TLBR". Throws ArgumentError on invalid direction or rows or cols
# not a power of 2.
def fold(directions, rows=16, cols=16)
check_rows_and_cols(rows, cols)

# build array of values
values = []
1.upto(rows*cols) do |i|
values <<
end

fold_matrix = FoldMatrix.new(values, rows, cols)

directions.each_byte do |fold_direction|
case fold_direction
when ?T
fold_matrix = fold_matrix.top_fold
when ?B
fold_matrix = fold_matrix.bottom_fold
when ?L
fold_matrix = fold_matrix.left_fold
when ?R
fold_matrix = fold_matrix.right_fold
else
raise ArgumentError, "Invalid direction #{fold_direction}"
end
end
fold_matrix.result
end

# Get the folding directions from a fold array. The item that has
# never been folded over is at end of array. The item that wasn't
# folded until the last fold and is now at at the first of array.
# Therefore...
#
# while size of array is greater than 1
# get direction in original matrix from last item number
# in folded array to first
# push direction on front of directions
# cut off front half of array
# end
#
# Throws ArgumentError on array not in fold order or rows or cols not
# power of 2.
def check_fold(folded, rows=16, cols=16)
check_rows_and_cols(rows, cols)

directions = ""
while folded.size > 1
# get direction in original matrix from last to first
direction = direction_to(folded.last, folded.first, rows, cols)

# and push it on front of directions
case direction
when :top
directions = "T" + directions
when :bottom
directions = "B" + directions
when :left
directions = "L" + directions
when :right
directions = "R" + directions
end

# cut array in half
folded = folded[folded.size/2...folded.size]
end
directions
end

if __FILE__ == $0
if (ARGV.size == 1 || ARGV.size == 3)
if (ARGV.size == 3)
rows = ARGV[1].to_i
cols = ARGV[2].to_i
else
rows = 16
cols = 16
end
p fold(ARGV[0], rows, cols)
else
puts "Usage: #$0 folds [rows cols]"
end
end
 
V

Vladimir Agafonkin

Of four solutions submitted so far, Luke's performs the best (2 times
better than the nearest neighbour - mine) - and the code is very clear
and easy to understand for a newbie like me. Great work!
 
G

Gregory Seidman

]
} I can't help feeling there should be a direct numerical way to
} calculate this sequence. To study the sequence, I wrote a second
} script, also attached, that prints the bits of the resulting sequence
} from any given rectangle dimensions and list of folds. (I subtract one
} from the sequence to make it zero-based.) However, even with a fair
} amount of studying lists of bit patterns I haven't cracked the code.

In fact, there is a very nice direct numerical way to calculate it. There
are a few key facts/insights:

1) Everything should be done zero-based (instead of one-based) until the
final output.

2) Given a width 2**N, only the lowest N bits change as one moves across
(horizontally) the grid.

3) Given a height 2**M, only the highest M bits change as one moves down
(vertically) the grid.

4) With a current (i.e. after any folds) width 2**n, every newly-touching pair
of numbers A and B are related by A == B ^ ((1<<n)-1) after a horizontal
(L or R) fold

5) With a current (i.e. after any folds) height 2**m and initial width
2**N, every newly-touching pair of numbers A and B are related by
A == B ^ (((1<<m)-1)<<N) after a vertical (T or B) fold.

6) The sequence of XOR operations that relate pairs of touching numbers is
a palindrome at any given moment.

7) The XOR sequence generated by a fold is the old sequence, then the XOR
value for the newest fold, then the old sequence again.

Thus, all we have to do is be able to generate the XOR value for a
particular fold (#4 & #5), generate the new sequence from that and the old
sequence (#7), keep track of where in the sequence the zero value is, and
keep track of whether the zero value is "face up" or "face down."

You take the final sequence and generate the actual numbers by XORing along
the sequence backward and forward from zero. Depending on whether zero is
face up or not, you may have to reverse the list of numbers. You then add
one to each number to get the one-based values instead of zero-based
values.

Note that this solution works for any width and height that are powers of
two, and need not be square. In addition, it could be trivially extended to
three or more dimensions. The code is below.

} Luke Blanshard
--Greg
P.S. Yes, I did add a method to the Enumerable module. It's unnecessary,
but convenient and kind of cute.

##### test63.rb ################################################################

require '63'

fail "Usage: #{__FILE__} <width> <height> <fold string>" if ARGV.length != 3
g = Grid.new(ARGV[0].to_i, ARGV[1].to_i)
p g.fold(ARGV[2])

##### 63.rb ####################################################################

module Enumerable
# Lisp-y!
def cdr
return self[1..-1]
end
end

module GridFolding

Opposite = {
"L" => "R",
"R" => "L",
"T" => "B",
"B" => "T"
}

IsXFold = {
"L" => true,
"R" => true,
"T" => false,
"B" => false
}

def validate_dims(x, y)
fail "x dimension must be at least 1" if x < 1
fail "y dimension must be at least 1" if y < 1
xbits = x.to_s(2).cdr
ybits = y.to_s(2).cdr
fail "x dimension must be a power of 2" if xbits.count("1") != 0
fail "y dimension must be a power of 2" if ybits.count("1") != 0
return [xbits.length, ybits.length]
end

def validate_folds(folds)
x_folds = folds.count("L") + folds.count("R")
y_folds = folds.count("T") + folds.count("B")
if folds.length != (x_folds + y_folds)
fail "Invalid characters in fold string"
else
if x_folds < @x_foldable
fail "Too few x folds"
elsif x_folds > @x_foldable
fail "Too many x folds"
end
if y_folds < @y_foldable
fail "Too few y folds"
elsif y_folds > @y_foldable
fail "Too many y folds"
end
end
return folds.split(//)
end

end

class Grid

def initialize(x, y)
@x_foldable, @y_foldable = validate_dims(x, y)
end

def fold(fold_str)
folds = validate_folds(fold_str.upcase)
zero_corner = ["T", "L"]
zero_slice = 0
operations = []
width = @x_foldable
height = @y_foldable
folds.each { |f|
if not zero_dir(zero_corner)
zero_slice += operations.length + 1
end
if zero_corner[0] == f
zero_corner[0] = Opposite[f]
elsif zero_corner[1] == f
zero_corner[1] = Opposite[f]
end
temp_ops = operations.clone()
op = 0
if IsXFold[f]
op = (1 << width) - 1
width -= 1
else
op = ((1 << height) - 1) << @x_foldable
height -= 1
end
operations << op
operations << temp_ops
operations.flatten!
}
below_zero = operations[0...zero_slice].reverse
above_zero = operations[zero_slice..-1]
curval = 0
below_zero.map! { |n| (curval ^= n) + 1 }
curval = 0
above_zero.map! { |n| (curval ^= n) + 1 }
list = []
if zero_dir(zero_corner)
list << above_zero.reverse
list << 1
list << below_zero
else
list << below_zero.reverse
list << 1
list << above_zero
end
return list.flatten!
end

private
include GridFolding

#true is up
def zero_dir(zero_corner)
not ((zero_corner[0]=="T") ^ (zero_corner[1]=="L"))
end
end

# vim:ts=2 sw=2 ai expandtab foldmethod=syntax foldcolumn=5
 
A

Andrew Dudzik

------=_Part_21417_17312780.1137951744677
Content-Type: multipart/alternative;
boundary="----=_Part_21418_25426328.1137951744677"

------=_Part_21418_25426328.1137951744677
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

Attached is my solution--I wish very much that I knew about Array#transpose
when I wrote this. Seems to work, though, although check_fold takes about =
8
seconds on my zippy Pentium III 750MHz--some restructuring could improve
that. From my observations, given a permutation of (1..256), there seems t=
o
be at most one sequence of folds that gives that result. i.e. there are
never two sequences that give the same permutation. Does anybody know why
this is? Seems like an interesting math problem.

I also really hacked together a get_dup method... how to you correctly tell
classes how to make copies of themselves?

------=_Part_21418_25426328.1137951744677
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

Attached is my solution--I wish very much that I knew about Array#transpose=
when I wrote this.&nbsp; Seems to work, though, although check_fold takes =
about 8 seconds on my zippy Pentium III 750MHz--some restructuring could im=
prove that.&nbsp; From my observations, given a permutation of (1..256), th=
ere seems to be at most one sequence of folds that gives that result.&nbsp;=
=20
i.e. there are never two sequences that give the same permutation.&nbsp; Do=
es anybody know why this is?&nbsp; Seems like an interesting math problem.<=
br><br>I also really hacked together a get_dup method... how to you correct=
ly tell classes how to make copies of themselves?
<br>

------=_Part_21418_25426328.1137951744677--

------=_Part_21417_17312780.1137951744677
Content-Type: image/rb; name=rubyquiz63.rb
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="rubyquiz63.rb"

class FoldedPaper
attr_reader :xlength,:ylength,:thickness
attr_accessor :sheets

@@dict = {'B' => 0, 'R' => 1, 'T' => 2, 'L' => 3}
@@rev_dict = {0 => 'B', 1 => 'R', 2 => 'T', 3 => 'L'}

def initialize(x,y,thickness = 1)
@xlength = x
@ylength = y
@thickness = thickness
@sheets = Array.new(thickness)
@sheets.each_index do |i|
@sheets = Array.new(y)
@sheets.each_index {|j| @sheets[j] = Array.new(x)}
end
end

def fill
raise if @thickness != 1
(0...@xlength).each {|a| (0...@ylength).each {|b| @sheets[0][a] = a+b*@xlength+1}}
end

def peek
@sheets.each_with_index do |sheet,i|
puts "Sheet " + i.to_s + ":"
sheet.each {|row| puts row.to_s}
puts ""
end
end

def rotate!
new_sheets = Array.new(@sheets.length)
new_sheets.each_index do |i|
new_sheets = Array.new(@xlength)
(0...@xlength).each do |a|
new_sheets[a] = Array.new(@ylength)
(0...@ylength).each {|b| new_sheets[a] = @sheets[@ylength-b-1][a]}
end
end
@xlength,@ylength = @ylength,@xlength
@sheets = new_sheets
end

def fold_bottom_to_top!
raise if @ylength % 2 != 0
new_sheets = Array.new(2*@thickness)
(0...@thickness).each do |i|
new_sheets = Array.new(@ylength/2)
new_sheets[i+@thickness] = Array.new(@ylength/2)
(0...@ylength/2).each do |b|
new_sheets = Array.new(@xlength)
new_sheets[i+@thickness][@ylength/2 - b-1] = Array.new(@xlength)
(0...@xlength).each do |a|
new_sheets[a] = @sheets[a]
new_sheets[i+@thickness][@ylength/2 - b-1][a] = @sheets[@thickness-i-1][@ylength/2+b][a]
end
end
end
@ylength /= 2
@thickness *= 2
@sheets = new_sheets
end

def fold!(k)o
k.times {rotate!}
fold_bottom_to_top!
(4-k).times {rotate!}
end

def unfold_bottom_to_top!
raise if @thickness % 2 != 0
new_sheets = Array.new(@thickness/2)
(0...@thickness/2).each do |i|
new_sheets = Array.new(2*@ylength)
(0...@ylength).each do |b|
new_sheets = Array.new(@xlength)
new_sheets[2*@ylength-b-1] = Array.new(@xlength)
(0...@xlength).each do |a|
new_sheets[a] = @sheets[a]
new_sheets[2*ylength-b-1][a] = @sheets[@thickness-i-1][a]
end
end
end
@ylength *= 2
@thickness /= 2
@sheets = new_sheets
end

def unfold!(k)
k.times {rotate!}
unfold_bottom_to_top!
(4-k).times {rotate!}
end

def readdown
raise if @xlength > 1 or @ylength > 1
(0...@thickness).map {|i| @sheets[@thickness-i-1][0][0]}
end

def writedown array
raise if @xlength > 1 or @ylength > 1 or @thickness != array.length
array.each_with_index {|v,i| @sheets[@thickness-i-1][0][0] = v}
end

def execute! command
command.split("").each {|c| fold! @@dict[c]}
end

def make_dup_of! fp
@sheets = fp.sheets.dup
@xlength = fp.xlength
@ylength = fp.ylength
@thickness = fp.thickness
end

def get_dup
fp = FoldedPaper.new(1,1)
fp.make_dup_of! self
fp
end

def equals? fp
return false if @xlength != fp.xlength or @ylength != fp.ylength or @thickness != fp.thickness
(0...@thickness).each do |i|
(0...@xlength).each do |a|
(0...@ylength).each do |b|
return false if @sheets[a] != fp.sheets[a]
end
end
end
return true
end

def is_valid? unfolded_x, unfolded_y
if @thickness == 1
fp = FoldedPaper.new(@xlength,@ylength)
fp.fill
return fp.equals?(self)
end

(0...@thickness).each do |i|
(0...@xlength).each do |a|
(0...@ylength).each do |b|
value = @sheets[a]
neighbors = [[a,b-1],[a,b+1],[a-1,b],[a+1,b]].select do |x,y|
x >= 0 and y >= 0 and x < @xlength and y < @ylength
end
neighbor_values = neighbors.map {|x,y| @sheets[y][x]}
if neighbor_values.detect {|n_val| not [1,unfolded_x].include?((value-n_val).abs)}
return false
end
end
end
end
return true
end

def find_solutions unfolded_x, unfolded_y
solutions = []
if is_valid? unfolded_x, unfolded_y
return [""] if @thickness == 1
(0..3).each do |k|
fp = get_dup
fp.unfold! k
if fp.xlength <= unfolded_x and fp.ylength <= unfolded_y
fp.find_solutions(unfolded_x,unfolded_y).each {|solution| solutions.push(solution + @@rev_dict[k])}
end
end
end
solutions
end
end

def fold(x,y,s)
fp = FoldedPaper.new(x,y)
fp.fill
fp.execute! s
fp.readdown
end

def check_fold x,y,array
fp = FoldedPaper.new(1,1,x*y)
fp.writedown array
fp.find_solutions(x,y)[0]
end
------=_Part_21417_17312780.1137951744677--
 
J

James Edward Gray II

P.S. Yes, I did add a method to the Enumerable module. It's
unnecessary,
but convenient and kind of cute.
module Enumerable
# Lisp-y!
def cdr
return self[1..-1]
end
end

But it has the downside of making Enumerable depend on a []() method,
which it normally doesn't require. ;)

Neat solution. Thanks for the nice walkthrough.

James Edward Gray II
 
N

Nathan Morse

Hello all,

Here's my solution. It's not terribly efficient (uses arrays of
arrays), but the algorithm should be pretty easy to read.

Cheers,

-Nathan



module Folding

def fold(h, w, commands)
page = Page.new_page_of_size(h, w)
commands.downcase.scan(/./).each do |command|
raise "Invalid input!" if page.invalid_command?(command)
page = page.send("fold_#{command}".to_sym)
end
raise "Invalid input!" if !page.is_one_cell?
page.first_cell
end

end

class Page

def self.new_page_of_size(h, w)
Page.new(create_page_map(h, w))
end

def height
@page_map.size
end

def width
@page_map.first.size
end

def fold_r
new_map = (1..height).inject([]) {|r, i| r << [] }
0.upto(height - 1) do |r|
0.upto(width / 2 - 1) do |c|
head = @page_map[r][c]
tail = @page_map[r][width - c - 1].reverse
new_map[r][c] = tail + head
end
end
Page.new(new_map)
end

def fold_l
turn_180.fold_r.turn_180
end

def fold_t
turn_cw.fold_r.turn_ccw
end

def fold_b
turn_ccw.fold_r.turn_cw
end

def turn_cw
new_map = (1..width).inject([]) {|r, i| r << [] }
0.upto(height - 1) do |r|
0.upto(width - 1) do |c|
new_map[c][height - r - 1] = @page_map[r][c]
end
end
Page.new(new_map)
end

def turn_ccw
turn_180.turn_cw
end

def turn_180
turn_cw.turn_cw
end

def invalid_command?(c)
height == 1 && (c == 't' || c == 'b') ||
width == 1 && (c == 'l' || c == 'r')
end

def is_one_cell?
height == 1 && width == 1
end

def first_cell
@page_map[0][0]
end

private

def initialize(map)
@page_map = map
end

def self.create_page_map(h, w)
(1..h).inject([]) do |page, i|
page << (1..w).inject([]) do |row, j|
row << [w*(i-1) + j]
end
end
end

end
 
S

Simon Kröger

Hi,

i hope there is something new in my solution. Rather than folding my
solution unfolds and keeps track of the position of a certain layer. It
starts with a 1x1 stack of paper and undos all cmds that leed to this
stack (doubling the size of the paper each step). Doing this for every
layer of the stack gives the solution to this quiz. (no arrays,
matrixes, etc. needed except for returning the result)

-----------------------------------------------------------------------
def unfold z, cmds
x, y, xdim, ydim, layer = 0, 0, 0.5, 0.5, 2**cmds.size

cmds.unpack('C*').reverse_each do |cmd|
x, xdim = x - xdim, xdim * 2 if cmd == ?R
x, xdim = x + xdim, xdim * 2 if cmd == ?L
y, ydim = y - ydim, ydim * 2 if cmd == ?B
y, ydim = y + ydim, ydim * 2 if cmd == ?T

if z > (layer /= 2)
z = 1 + (layer * 2) - z
x = -x if cmd == ?R || cmd == ?L
y = -y if cmd == ?B || cmd == ?T
end
end
(xdim + x + 0.5 + (ydim + y - 0.5) * xdim * 2).to_i
end

def fold xsize, ysize, cmds
raise RuntimeError if cmds.scan(/[^RLBT]/).size.nonzero?
raise RuntimeError if 2**cmds.scan(/[RL]/).size != xsize
raise RuntimeError if 2**cmds.scan(/[BT]/).size != ysize

(1..(xsize * ysize)).map{|z| unfold(z, cmds)}.reverse
end

puts fold(16, 16, 'TLBLRRTB')
 
V

Vance Heron

My solution.

#! /usr/bin/env ruby

def fold row_siz, cmds
# paper is an array of layers,
# each layer is an array of rows,
# each row is an array of integers

paper = []
layer = []
1.upto(row_siz){|i|
row = []
1.upto(row_siz){|j| row << j + row_siz*(i-1)}
layer << row
}
paper = [ layer ]

nfold = (Math.log(row_siz)/Math.log(2)) # Number of folds each direction

# validate inputs
raise "Array size not a power of 2" unless 2**nfold == row_siz
raise "Invalid cmd length" unless cmds.length == nfold * 2
raise "Invalid fold chars" unless cmds.scan(/[TBLR]/).length == nfold * 2
raise "Invalid fold list" unless cmds.scan(/[TB]/).length == nfold

cmds.split(//).each{|f|
new_paper = []
case f
when 'L','R'
row_siz = paper[0][0].length/2
s1, s2 = (f == 'L') ? [0,row_siz] : [row_siz,0]
paper.reverse.each { |layer|
new_layer = []
layer.each {|row|
new_layer << row.slice(s1,row_siz).reverse
}
new_paper << new_layer
}
paper.each { |layer|
new_layer = []
layer.each {|row|
new_layer << row.slice(s2,row_siz)
}
new_paper << new_layer
}
when 'T','B'
col_siz = paper[0].length/2
s1, s2 = (f == 'T') ? [0,col_siz] : [col_siz,0]
paper.reverse.each { |layer|
new_paper << layer.slice(s1,col_siz).reverse
}
paper.each { |layer|
new_paper << layer.slice(s2, col_siz)
}
end
paper = new_paper
}
return paper.flatten
end

def usage
puts "Usage #{File.basename($0)} <grid sz> <fold list>"
puts " grid sz must be power of 2"
puts " valid fold are T, B, R, L"
puts " you must have enough folds to get NxN to 1x1"
exit
end

usage unless ARGV.length == 2

row_siz = ARGV[0].to_i
cmds = ARGV[1]

res = fold(row_siz, cmds)
puts "RES"
puts "[ #{res.join(', ')} ]"
 
V

Vladimir Agafonkin

Brilliant idea, Simon! Though it's somewhat slow comparing to other
solutions, I guess it has a very low memory consumption instead.

Still, my favourite is Sander's - very simple and short and still quite
performant.

Gregory's solution is the fastest (and thats great), however, it is too
big and complicated for only a ~100% performance boost over Sander's,
IMHO.
 
A

Andrew Dudzik

------=_Part_22002_2627173.1137958322743
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

That's a neat way to go about it. That's how I wrote my
check_fold--exploring backwards from the final stack, and throwing out path=
s
that were obviously wrong--but I never thought of implementing the entire
thing using backwards folds.

Hi,

i hope there is something new in my solution. Rather than folding my
solution unfolds and keeps track of the position of a certain layer. It
starts with a 1x1 stack of paper and undos all cmds that leed to this
stack (doubling the size of the paper each step). Doing this for every
layer of the stack gives the solution to this quiz. (no arrays,
matrixes, etc. needed except for returning the result)

------=_Part_22002_2627173.1137958322743--
 
D

Dominik Bathon

Here is mine.

It supports square grids of all size, the size is "autodetected" from the=
=20
length of the fold string.

Dominik


# A simple 2D array, the width and height are immutable once it is create=
d.
class Array2D
attr_reader :w, :h
def initialize(w, h, init_array =3D nil)
@w, @h=3Dw, h
@array =3D init_array || []
end
def [](x, y)
@array[(y%h)*w+(x%w)]
end
def []=3D(x, y, v)
@array[(y%h)*w+(x%w)]=3Dv
end
end

class GridFold
attr_reader :grid

# the initial grid will be (2**n)x(2**n)
def initialize(n =3D 4)
d =3D 1 << n
@grid =3D Array2D.new(d, d, (1..(d*d)).map { |i| })
end

def fold_t
fold_help(@grid.w, @grid.h / 2) { |x, y, _, nh|
@grid[x, nh-1-y].reverse + @grid[x, nh+y]
}
end
def fold_b
fold_help(@grid.w, @grid.h / 2) { |x, y, _, _|
@grid[x, @grid.h-1-y].reverse + @grid[x, y]
}
end
def fold_l
fold_help(@grid.w / 2, @grid.h) { |x, y, nw, _|
@grid[nw-1-x, y].reverse + @grid[nw+x, y]
}
end
def fold_r
fold_help(@grid.w / 2, @grid.h) { |x, y, _, _|
@grid[@grid.w-1-x, y].reverse + @grid[x, y]
}
end

def self.fold(folds_str)
folds =3D folds_str.to_s.downcase
n =3D folds.size / 2
unless folds =3D~ /\A[tblr]*\z/ && folds.size =3D=3D n * 2
raise "invalid argument"
end
gf =3D self.new(n)
folds.scan(/./) { |f| gf.send("fold_#{f}") }
gf.grid[0, 0]
end

private

def fold_help(new_width, new_height)
raise "impossible" unless new_width >=3D 1 && new_height >=3D 1
new_grid =3D Array2D.new(new_width, new_height)
new_width.times { |x| new_height.times { |y|
new_grid[x, y] =3D yield x, y, new_width, new_height
} }
@grid =3D new_grid
self
end
end

if $0 =3D=3D __FILE__
begin
p GridFold.fold(ARGV.shift.to_s)
rescue =3D> e
warn e
exit 1
end
end
 
L

Luke Blanshard

Vladimir said:
Of four solutions submitted so far, Luke's performs the best (2 times
better than the nearest neighbour - mine) - and the code is very clear
and easy to understand for a newbie like me. Great work!
Wow, that's excellent. I would have thought that the prepending
("unshift") I do would have slowed things down. But I do reuse all the
arrays wherever possible, and that probably makes the difference.

Now I'm working on a variant of Greg Seidman's approach. It should be
lots faster yet, but the tradeoff is it's much less clear.

Luke
 
V

Vladimir Agafonkin

Good, though your solution is still not THAT slower then Greg's:

C:\eclipse\workspace\quizes\other>fold_luke.rb
Each operation will run 1000 times.
2x2 sheet folding done in 0.2 seconds
4x4 sheet folding done in 0.491 seconds
8x8 sheet folding done in 1.282 seconds
16x16 sheet folding done in 3.915 seconds

C:\eclipse\workspace\quizes\other>fold_greg.rb
Each operation will run 1000 times.
2x2 sheet folding done in 0.381 seconds
4x4 sheet folding done in 0.581 seconds
8x8 sheet folding done in 1.071 seconds
16x16 sheet folding done in 2.744 seconds

PS: I tried using memoize library to evaluate the performance boost,
and the results are quite interesting:

(with memoize)
Each operation will run 1000 times.
2x2 sheet folding done in 0.02 seconds
4x4 sheet folding done in 0.01 seconds
8x8 sheet folding done in 0.02 seconds
16x16 sheet folding done in 0.03 seconds
32x32 sheet folding done in 0.04 seconds
64x64 sheet folding done in 0.1 seconds
128x128 sheet folding done in 0.341 seconds
256x256 sheet folding done in 1.372 seconds

(without)
Each operation will run 1000 times.
2x2 sheet folding done in 0.241 seconds
4x4 sheet folding done in 0.56 seconds
8x8 sheet folding done in 1.703 seconds
16x16 sheet folding done in 5.818 seconds
(didn't go further, the things are already clear enough)
 
L

Luke Blanshard

--------------090700010508010409000101
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Gregory said:
]
} I can't help feeling there should be a direct numerical way to
} calculate this sequence. To study the sequence, I wrote a second
} script, also attached, that prints the bits of the resulting sequence
} from any given rectangle dimensions and list of folds. (I subtract one
} from the sequence to make it zero-based.) However, even with a fair
} amount of studying lists of bit patterns I haven't cracked the code.

In fact, there is a very nice direct numerical way to calculate it. There
are a few key facts/insights: ...
I see your insights, and raise you a couple.

* If you associate the XOR masks with the folds, ie arranging them
in an array whose size is the number of folds, then you can choose
which mask to use for a given output element by indexing into this
array. The order of these indexes is like this: 0, 1, 0, 2, 0, 1,
0, 3, 0, 1, ... If this looks familiar, it is because it is
related to counting in binary arithmetic.
* The folds quickly and easily determine the bottom grid square:
just translate them to bits, with the forward folds (T and L)
being 1 and backwards ones being 0, and then arrange the bits with
the vertical folds' bits on top.
* The XOR of the first and last elements of the answer is the last
mask in our array of masks.

With all of this, I have a solution that generates the output in
sequence without any simulation of folding, or generating extraneous
arrays. And it was easy to turn this into an unfolder as well.

The downside is that it is just as long as my original solution, and it
is completely incomprehensible. But I bet it's plenty fast!

Luke Blanshard

--------------090700010508010409000101
Content-Type: text/plain;
name="fold2.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="fold2.rb"

#!/usr/bin/ruby -w
#
# Ruby Quiz #63, Grid Folding, take 2

# Creates XOR masks for the folds, generates the sequence directly.
def fold2 h, v, folds
hbits, vbits = folds.count("LR"), folds.count("TB")
raise "Illegal folds #{folds}" unless hbits+vbits == folds.length
raise "Folds imply #{1<<hbits}x#{1<<vbits}, not #{h}x#{v}" unless 1<<hbits == h && 1<<vbits == v
hmask, vmask = (1 << hbits) - 1, (1 << vbits) - 1
masks, final_h, final_v = [], 0, 0
folds.split(//).each do |fold|
if fold =~ /[LR]/
masks << hmask; hmask /= 2
final_h = final_h*2 + (fold=="L"?1:0)
else
masks << (vmask << hbits); vmask /= 2
final_v = final_v*2 + (fold=="T"?1:0)
end
end
answer = [(n = (final_v<<hbits|final_h) ^ masks[-1]) + 1]
while true
i = answer.length.top_changed_bit
return answer if i >= masks.length
answer << (n ^= masks) + 1
end
end

# Takes a sequence generated by folding, reproduces the fold
# instructions (as best as possible) by recreating the masks.
def unfold seq
nfolds = seq.size.log2
mask = (seq[0]-1) ^ (seq[1]-1)
hbits = ((mask.odd??mask : mask^((1<<nfolds)-1)) + 1).log2
hfolds, vfolds = [], []
final = seq[-1] - 1
(0...hbits).each{|bit| hfolds.push(final[bit].odd?? "L":"R")}
(hbits...nfolds).each{|bit| vfolds.push(final[bit].odd?? "T":"B")}
folds = ""
i = 1
while i < seq.size
mask = (seq[i-1]-1) ^ (seq-1)
folds.insert( -1, (mask.odd??hfolds:vfolds).pop )
i *= 2
end
folds
end

class Integer
# Returns the number of the top bit that changed from self-1
def top_changed_bit; (self^(self-1)).log2 end
def log2
raise "Domain error" if (n=self) <= (answer=0)
while n > 1: answer += 1; n /= 2 end
answer
end
def odd?; return self[0] == 1 end
end

require "fold"

# Main program
if $0 == __FILE__
h, v, folds = get_args
a2 = fold2 h, v, folds
a = fold h, v, folds
raise "New folder returned\n #{a2.inspect}\nwhile old one returned\n #{a.inspect}" unless a == a2
unfolds = unfold a
raise "Unfolding returned #{unfolds}" unless unfolds == folds or unfolds.tr("TBLR","LRTB") == folds
p a
p unfolds
end

--------------090700010508010409000101--
 

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

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top