On Sun, Jan 22, 2006 at 10:55:44PM +0900, Luke Blanshard wrote:
[...]
}  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