オフラインリアルタイムどう書くE28の実装例 ( Ruby )

オフラインリアルタイムどう書くE28の問題「増築の果ての隣室」の実装例を、Ruby で。

問題 : http://nabetani.sakura.ne.jp/hena/orde28sqst/
実装リンク集 : https://qiita.com/Nabetani/items/4e3fac2746ca05a93165
イベント : https://yhpg.doorkeeper.jp/events/81346

めちゃ面白いイベントなのに参加者すくなくて寂しいので参加者募集です!!!次回のイベントももう公開済みなのでぜひ。

yhpg.doorkeeper.jp

負けました。正確には時間切れから発表中デバッグでバグを落としたけど計算量減らしてないのでNo30くらいまでしか通せず。家帰ってちょこちょこ直していまは通ってます。以下、ざっくり方針。

なんか計算で番号求められるんじゃね?って思ったんだけど、増築されていくごとに分割数が増えていくことにより、1辺の長さを割っていったもの(あるいはかけていったもの)が膨らんでいって、どうもさくっとは計算できなさそうだった。なので真面目に増築することにする。

Ruby なので有理数が扱えて最初の正方形の1辺を1としたときに4分割なら 1/4 、そのあと7分割なら 4/28 みたいな感じで考えていくと楽かも、と思った。ただどうも隣接の判定をミスりそうな気がしたので4分割したら最初の正方形の1辺を4、さらに7分割なら28と増やしていくことにした。Rubyなら扱いきれるだろ、みたいな雑な予想込みで。

んで、そうやってかけていった値をもとにすると、すべての部屋の辺の長さが整数で扱えるようになる。じゃあそのあとどうするのっていうと、 Hash{Integer => Hash{ Integer => Integer}} みたいな型で盤面を用意してほんとに正方形を塗っていくことにした。配列にしてないのはSあるいはW方向への増築をすると x, y がマイナス側にいくかもだから。全く塗る必要はないのだが脳内デバッグが下手すぎて実際に塗ってたらなんかわかるようになってきた。実際発表中に debug モードで見せたりしてウケた1

ただこれがとんでもなく遅い。そりゃ当然だということで全部塗るわけじゃなくて辺だけ塗るようにした。

でもこれも遅い。辺すら塗る必要ないよね、といって部屋の情報を貯め込むようにした。

これもまた遅い。これがなんで遅いのかというと隣接判定のところで r1.to_a & r2.to_a 的なことをやってたせいだった。そりゃそうだ。隣接判定のため、x座標的にあるいはy座標的に同じところにいるかどうか、みたいな判定。

    def neighbor?(other)
      if h_match?(other)
        x2 == other.x1 || x1 == other.x2
      elsif v_match?(other)
        y2 == other.y1 || y1 == other.y2
      end
    end
     def v_match?(other)
      !((x1...x2).to_a & (other.x1...other.x2).to_a).empty?
    end
     def h_match?(other)
      !((y1...y2).to_a & (other.y1...other.y2).to_a).empty?
    end

重なりがあること、というのがうまく言語化できなかったのだが両手をピースにしてごにょごにょ動かしてたら、一方の始点がもう一方の終点より大きいケースがだめであることがわかった。というわけで最終形は以下。これは普通に返ってくる。 x1, x2, y1, y2 の計算あたりは正方形なので全然いらない情報なのだが一旦通ってるのでこのままで。

class Solver
  Instruction = Struct.new(:dir, :size)
  Sq = Struct.new(:num, :x1, :x2, :y1, :y2) do
    def cover?(x, y)
      x1 <= x && x <= x2 && y1 <= y && y <= y2
    end

    def neighbor?(other)
      if h_match?(other)
        x2 == other.x1 || x1 == other.x2
      elsif v_match?(other)
        y2 == other.y1 || y1 == other.y2
      end
    end

    def v_match?(other)
      !((x2 - 1) < other.x1 || (other.x2 - 1) < x1)
    end

    def h_match?(other)
      !((y2 - 1) < other.y1 || (other.y2 - 1) < y1)
    end
  end

  class Board
    attr_reader :min_w, :max_w, :min_h, :max_h
    def initialize
      @min_w = 0
      @max_w = 0
      @min_h = 0
      @max_h = 0
      @sqs = []
    end

    def fetch(x, y)
      @sqs.find { |e| e.cover?(x, y) }&.num
    end

    def neighbors(num)
      t = @sqs.find { |e| e.num == num }
      @sqs.select { |e| t.neighbor?(e) }.sort_by(&:num)
    end

    def rect(num, x1, x2, y1, y2)
      @min_w = x1 if x1 < min_w
      @max_w = x2 if x2 > max_w
      @min_h = y1 if y1 < min_h
      @max_h = y2 if y2 > max_h
      @sqs << Sq.new(num, x1, x2, y1, y2)
    end
  end

  def initialize
    @base = 1 # base_size
  end

  def solve(input)
    a, b = input.split('/', 2)
    @target = b.to_i
    @insns = a.chars.each_slice(2).map do |s|
      Instruction.new(s[0].to_sym, s[1].to_i)
    end

    @insns.each do |insn|
      @base *= insn.size
    end

    board = Board.new
    board.rect(1, 0, @base, 0, @base)

    n = 1

    @insns.each.with_index do |insn, i|
      minw = board.min_w
      maxw = board.max_w
      minh = board.min_h
      maxh = board.max_h
      case insn.dir
      when :N, :S
        tmp_base = (maxw - minw) / insn.size
      else
        tmp_base = (maxh - minh) / insn.size
      end
      1.upto(insn.size) do |d|
        case insn.dir
        when :N
          x2 = minw + (tmp_base * d)
          x1 = x2 - tmp_base
          y1 = maxh
          y2 = maxh + tmp_base
        when :S
          x2 = minw + (tmp_base * d)
          x1 = x2 - tmp_base
          y2 = minh
          y1 = minh - tmp_base
        when :E
          y1 = maxh - (tmp_base * d)
          y2 = y1 + tmp_base
          x1 = maxw
          x2 = maxw + tmp_base
        when :W
          y1 = maxh - (tmp_base * d)
          y2 = y1 + tmp_base
          x2 = minw
          x1 = minw - tmp_base
        end

        board.rect(n+d, x1, x2, y1, y2)
      end
      n += insn.size
    end

    board.neighbors(@target).map(&:num).join(',')
  end
end