Hi all,
here's another solution. When I read the quiz my first thought was about using
regular expressions. So I first tried to extend gsub! to work both horizontally
as well as vertically through a multi-line string. As others, I too had to solve
some unexpected hurdles. I hope you find this version interesting.
Regards,
Pit
class Array
# converts a two-dimensional array into a multi-line string
def to_s2
map { |row| "#{row}\n" }.join
end
# inserts successive elements from other array between every two
# elements of this array
def weave other
inject [] do |array, element|
array << other.shift unless array.empty?
array << element
end
end
# weaves the rows of two two-dimensional arrays, see weave
def weave2 other
zip( other ).map { |row1, row2| row1.weave row2 }
end
end
class String
# splits a multi-line string into a two-dimensional array of
# character strings
def to_a2
map { |row| row.chomp.split // }
end
# transposes a multi-line string like a two-dimensional array
def transpose
to_a2.transpose.to_s2
end
# in-place form of transpose
def transpose!
replace transpose
end
# like gsub!, but works both horizontally and vertically in a
# multi-line string
def gsub2! re, subst
result1 = gsub! re, subst
transpose!
result2 = gsub! re, subst
transpose!
self if result1 || result2
end
# like Array#weave2, using multi-line strings
def weave other
to_a2.weave2( other.to_a2 ).to_s2
end
end
# input patterns
FILLED = "X"
LETTER = "_"
# intermediate patterns
NUMBER = "N"
OUTSIDE = "O"
LINE = "#"
EMPTY = " "
# search patterns
OUT_OR_EDGE = /^|#{OUTSIDE}|$/
BEFORE_NEW_WORD = /^|#{OUTSIDE}|#{FILLED}/
WORD = /#{LETTER}|#{NUMBER}/
# removes spaces from input
def remove_spaces
@s.gsub!( / /, "" )
end
# marks outside cells (filled cells adjacent to edges or outside cells)
def mark_outside_cells
nil while \
@s.gsub2!( /#{FILLED}(#{OUT_OR_EDGE})/, "#{OUTSIDE}\\1" ) or \
@s.gsub2!( /(#{OUT_OR_EDGE})#{FILLED}/, "\\1#{OUTSIDE}" )
end
# marks beginnings of words
def number_beginnings
@s.gsub2! /(#{BEFORE_NEW_WORD})#{LETTER}(#{WORD})/, "\\1#{NUMBER}\\2"
end
# creates a pattern of delimiters between non-outside cells
def delimiters s, delimiter, space, gsub_method = :gsub!
r = s.dup
r.send gsub_method, /^/, OUTSIDE
r.send gsub_method, /[^#{OUTSIDE}\n]/, delimiter
r.send gsub_method, /#{OUTSIDE}(?=#{delimiter})/, delimiter
r.send gsub_method, OUTSIDE, space
r
end
# duplicates every cell line, removing number marks
def duplicate_cell_lines
index = 0
@s = @s.inject do |result, row|
index += 1
result << row
result << row.gsub( NUMBER, LETTER ) if index % 2 == 1
result
end
end
# read layout
@s = ARGF.read
# process the layout
remove_spaces
mark_outside_cells
number_beginnings
# delimiting lines and corners
v = delimiters @s, LINE, EMPTY
h = delimiters( @s.transpose, FILLED, LETTER ).transpose
c = delimiters @s, LINE, EMPTY, :gsub2!
# combine layout and delimiters
vs = v.weave @s
ch = c.weave h
@s = ch.transpose.weave( vs.transpose ).transpose
# format the result
duplicate_cell_lines
num = 0
@s.gsub! /./ do |char|
case char
when OUTSIDE, LETTER then " "
when FILLED then "####"
when NUMBER then "%-4i" % ( num += 1 )
else char
end
end
# output the result
puts @s