Bill.Dolinar
1/22/2006 2:46:00 PM
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 << [i]
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