🙌

2次元フィールドをオブジェクトで表現する

2021/07/03に公開

Why

競プロによくある入力がH x Wであたえられるようなデータ構造を単純に2次元の配列として扱うと
範囲外アクセスのチェックなどが煩雑だなと感じたので、オブジェクトとしての表現を考えてみました。

実装

<<~NOTE
	c	c	c W
r	0	1	2
r	3	4	5
r	6	7	8
H
NOTE
class Field
  attr_reader :height, :width

  def initialize(h, w, init_val = nil)
    @height = h
    @width = w
    @field = Array.new(h * w, init_val)
  end

  def get(r, c)
    return nil if r < 0 || c < 0
    return nil if r >= height || c >= width

    @field[pos(r, c)]
  end
  alias [] get

  def get!(r, c)
    raise if r < 0 || c < 0
    raise if r >= height || c >= width

    @field[pos(r, c)]
  end

  def set(r, c, val)
    return false if r < 0 || c < 0
    return false if r >= height || c >= width

    @field[pos(r, c)] = val

    true
  end

  def set!(r, c, val)
    set(r, c, val) || raise
  end
  alias []= set!

  def row(r)
    @field[r*width, width]
  end

  def col(c)
    arr = []
    while (c < width * height)
      arr << @field[c]

      c += width
    end

    arr
  end

  def rows
    Enumerator.new do |y|
      height.times do |r|
        y << row(r)
      end
    end
  end

  def cols
    Enumerator.new do |y|
      width.times do |c|
        y << col(c)
      end
    end
  end

  def dump
    @field.each_slice(width){|a| p a }
  end

  private

  def pos(r, c)
    r * width + c
  end
end

example

H = 10
W = 4
field = Field.new(H, W, 0)

H.times do |r|
  W.times do |c|
    field.set(r, c, (r + c)**2)
  end
end

field[3, 0] = 1000
puts field[3, 0]

field.dump

p field.row(0)
p field.row(3)
p field.col(0)
p field.col(1)

field.rows.each do |row|
  puts row.sum
end

field.cols.each do |col|
  puts col.sum
end

Discussion