Forum: Ruby Fwd: Grid Folding (#63)

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2006-01-22 19:21
(Received via mailing list)
Begin forwarded message:
81d609425e306219d54d793a0ad98bce?d=identicon&s=25 Matthew Moss (Guest)
on 2006-01-22 23:13
(Received via mailing list)
Here is my own solution, using 3-deep nested arrays. I could clean
this up a bit more, I think, but I am looking at another approach I
want to try before I summarize later this week.


class Integer
   def pow2?
      (self & (self-1)) == 0
   end
end


class Paper
   def build(w, h)
      Array.new(h) do |r|
         Array.new(w) do |c|
            yield r, c
         end
      end
   end

   def initialize(w, h)
      raise "Bad dimensions: #{w}x#{h}" unless w.pow2? and h.pow2?
      @rows = build(w, h) { |r,c| [w*r + c + 1] }
   end

   def rows
      @rows.dup
   end

   def cols
      build(@rows.size, @rows[0].size) { |c,r| @rows[r][c] }
   end

   def v_fold(i)
      rs = rows
      hh = rs.size / 2
      raise "Too many vertical folds." if hh == 0
      above, below = rs[i*hh, hh].reverse, rs[(1-i)*hh, hh]
      build(above[0].size, above.size) do |r, c|
         above[r][c].reverse.concat below[r][c]
      end
   end

   def h_fold(i)
      cs = cols
      hw = cs.size / 2
      raise "Too many horizontal folds." if hw == 0
      above, below = cs[i*hw, hw].reverse, cs[(1-i)*hw, hw]
      build(above.size, above[0].size) do |c, r|
         above[r][c].reverse.concat below[r][c]
      end
   end

   def fold!(cmds)
      cmds.each_byte do |cmd|
         case cmd
         when ?T
            @rows = v_fold(0)
         when ?B
            @rows = v_fold(1)
         when ?L
            @rows = h_fold(0)
         when ?R
            @rows = h_fold(1)
         end
      end
      self
   end

    def result
      raise "Not enough folds!" unless @rows[0][0] == @rows.flatten
      @rows[0][0]
   end
end


def fold(w, h, cmds)
   Paper.new(w, h).fold!(cmds).result
end
81d609425e306219d54d793a0ad98bce?d=identicon&s=25 Matthew Moss (Guest)
on 2006-01-23 17:16
(Received via mailing list)
> Here is my own solution, using 3-deep nested arrays. I could clean
> this up a bit more, I think, but I am looking at another approach I
> want to try before I summarize later this week.

Well, I ended up cleaning up my submission a bit anyway. I still have
another approach in mind, but making a few changes to get this was
pretty easy.  Now, I basically encode only one kind of fold
(top-to-bottom), and turn the paper a few times before and after the
fold to get the desired results.

I think I saw another approach do something very similar if not the
same, but I swear I didn't cheat!  =)


class Integer
   def pow2?
      (self & (self-1)).zero? and not self.zero?
   end

   def even?
      (self & 1).zero?
   end
end


class Array
   def reflect    # vertical
      self.reverse
   end

   def turn       # 90 deg CCW
      self.transpose.reflect
   end

   def fold       # top to bottom
      raise "Invalid fold." unless size.even?
      w, hh = self[0].size, size / 2
      above, below = self[0, hh].reverse, self[hh, hh]
      Array.build_2d(w,hh) { |r,c| above[r][c].reverse.concat
below[r][c] }
   end

   def Array.build_2d(w, h)
      Array.new(h) do |r|
         Array.new(w) do |c|
            yield r, c
         end
      end
   end
end


class Paper
   def initialize(w, h)
      raise "Bad dimensions: #{w}x#{h}" unless w.pow2? and h.pow2?
      @rows = Array.build_2d(w, h) { |r,c| [w*r + c + 1] }
   end

   def fold!(cmds)
      cmds.each_byte do |cmd|
         case cmd
         when ?T
            @rows = @rows.fold
         when ?B
            @rows = @rows.turn.turn.fold.turn.turn
         when ?L
            @rows = @rows.turn.turn.turn.fold.turn
         when ?R
            @rows = @rows.turn.fold.turn.turn.turn
         end
      end
      raise "Not enough folds!" unless @rows[0][0] == @rows.flatten
      @rows[0][0]
   end
end


def fold(w, h, cmds)
   Paper.new(w, h).fold!(cmds)
end
81d609425e306219d54d793a0ad98bce?d=identicon&s=25 Matthew Moss (Guest)
on 2006-01-23 17:16
(Received via mailing list)
Just realized I now use my Array.reflect in only one place, and it
only does a reverse, so that should be refactored out.
81d609425e306219d54d793a0ad98bce?d=identicon&s=25 Matthew Moss (Guest)
on 2006-01-23 17:19
(Received via mailing list)
Actually ....  (and I feel like I'm saying this just for my own
benefit, please forgive my rambling) ...  now that I put most
operations into Array, the Paper class is mostly pointless, since I
wanted to change mutating fold! to non-mutating fold (esp considering
its return type), and I might as well then just move the dimension
check, iteration and fold processing into the global fold function
anyway.

Silly me.
81d609425e306219d54d793a0ad98bce?d=identicon&s=25 Matthew Moss (Guest)
on 2006-01-23 17:25
(Received via mailing list)
On that note (i.e., that I'm talking to myself), here is the final
3-deep nested array version.  I won't touch this again.


class Integer
   def pow2?
      (self & (self-1)).zero? and not self.zero?
   end

   def even?
      (self & 1).zero?
   end
end


class Array
   def reflect    # vertical
      self.reverse
   end

   def turn       # 90 deg CCW
      self.transpose.reflect
   end

   def fold       # top to bottom
      raise "Invalid fold." unless size.even?
      w, hh = self[0].size, size / 2
      above, below = self[0, hh].reverse, self[hh, hh]
      Array.new_2d(w,hh) { |r,c| above[r][c].reverse.concat below[r][c]
}
   end

   def Array.new_2d(w, h)
      Array.new(h) do |r|
         Array.new(w) do |c|
            yield r, c
         end
      end
   end
end


def fold(w, h, cmds)
   raise "Bad dimensions: #{w}x#{h}" unless w.pow2? and h.pow2?
   paper = Array.new_2d(w, h) { |r,c| [w*r + c + 1] }

   cmds.each_byte do |cmd|
      case cmd
      when ?T
         paper = paper.fold
      when ?R
         paper = paper.turn.fold.turn.turn.turn
      when ?B
         paper = paper.turn.turn.fold.turn.turn
      when ?L
         paper = paper.turn.turn.turn.fold.turn
      end
   end

   raise "Not enough folds!" unless paper[0][0] == paper.flatten
   paper[0][0]
end
0d298cda3121e5cacaa2465437769025?d=identicon&s=25 Levin Alexander (Guest)
on 2006-01-23 17:41
(Received via mailing list)
On 1/23/06, Matthew Moss <matthew.moss.coder@gmail.com> wrote:

>    paper = Array.new_2d(w, h) { |r,c| [w*r + c + 1] }

require 'enumerator'
paper = (1..w*h).to_enum(:each_slice, w).to_a

Levin, :)
4d7bf65b4675b3e9575617929ab123f1?d=identicon&s=25 Paul Novak (Guest)
on 2006-01-24 04:01
(Received via mailing list)
This was a lot of fun, but I never got to the extra-extra credit.

My Grid holds an array of arrays of Cell objects.  Using
Array#transpose it was easy to switch back and forth between an array
of rows or an array of columns to perform a fold operation was either
horizontal (row-wise) or vertical (column-wise).

Each of the fold methods operates on the Cells that lie on the top
surface of the Grid.  To  start, that is all of them.  After a single
L-fold, it would be the (back of) the left half of all the rows.

Since a fold brings facing Cells together, touching Cells are linked
to one another.  To find the Cells that are now on the top face, just
follow the chain of links from the appropriate half (left half for an
L-fold, say) to the other end of the chain.  Those Cells will form the
new face which is operated on by the next fold instruction, and so on,
until a single Cell remains on the face.

To get the answer, just follow the chain of links from that single
face Cell through all the Cells to the end.

Hopefully, the foregoing makes sense.  If not, the code is probably
easier to read...

def fold folds, rows = 16, cols = 16
  validate folds, rows, cols
  grid = Grid.new(rows, cols)
  folds.scan(/./){|op|grid.send(op)}
  return grid.list_values
end

def validate folds, rows, cols
  lr_folds = folds.count("LR")
  tb_folds = folds.count("TB")
  while rows>1
    rows/=2.0
    tb_folds-=1
  end
  while cols>1
    cols/=2.0
    lr_folds-=1
  end
  if rows!=1.0 or cols!=1.0 or tb_folds!=0 or lr_folds!=0
    fail "invalid fold instructions"
  end
end

class Grid
  attr_reader :face_rows, :face_cols
  def initialize (row_count, col_count)
    # set up array of arrays of Cells that can be transposed between
rows or columns
    @face_rows = Array.new(row_count) { |i|
     ((i*col_count+1)..((i+1)*col_count)).map{|v|Cell.new(v)}}.map{|r|r.to_a}
    @face_cols = @face_rows.transpose # this is just too easy
  end
  ## L,R,T,B could probably be refactored into one method, but this
works...
  def L
    new_face = []
    @face_rows.each do |a|
      new_row=[]
      while c2 = a.pop
        c1 = a.shift
        new_row << c1.get_chain[-1]
        c1.link_to c2
        c2.link_to c1
      end
      new_face << new_row.reverse  # since folding flips it over
    end
    @face_rows = new_face
    @face_cols = @face_rows.transpose
  end
  def R
    new_face = []
    @face_rows.each do |a|
      new_row = []
      while c2 = a.pop
        c1 = a.shift
        new_row << c2.get_chain[-1]
        c1.link_to c2
        c2.link_to c1
      end
      new_face << new_row
    end
    @face_rows = new_face
    @face_cols = @face_rows.transpose
  end
  def T
    new_face = []
    @face_cols.each do |a|
      new_col=[]
      while c2 = a.pop
        c1 = a.shift
        new_col << c1.get_chain[-1]
        c1.link_to c2
        c2.link_to c1
      end
      new_face << new_col.reverse  # since folding flips it over
    end
    @face_cols = new_face
    @face_rows = @face_cols.transpose
  end
  def B
    new_face = []
    @face_cols.each do |a|
      new_col=[]
      while c2 = a.pop
        c1 = a.shift
        new_col << c2.get_chain[-1]
        c1.link_to c2
        c2.link_to c1
      end
      new_face << new_col
    end
    @face_cols = new_face
    @face_rows = @face_cols.transpose
  end

  def list_values
    @face_rows.flatten.first.get_chain.map{|x|x.value}
  end
end

class Cell

  attr_reader :links, :value
  def initialize value
    @value = value
    @links = []
  end
  def link_to c
    @links << c
  end
  def get_chain start_cell=self
    next_cell = @links.reject{|x|x==start_cell}.last
    next_cell ? [self] + next_cell.get_chain(self) : [self]
  end
  def inspect
    @value
  end
end
This topic is locked and can not be replied to.