オフラインリアルタイムどう書くE13「六角形のテトロミノ」をRubyで解いてみた。

# 問題

http://nabetani.sakura.ne.jp/hena/orde13hextet/

六角形のマスをつなげたハニカム構造のマス目がある。
入力は、４つのマス目を示す文字列で指定される。
出力は、入力値が形成する図形が、リンク先の図形に該当する場合は識別子を、しない場合は`-`を返す。

# 考え方

任意のマス目から東、南東、南西、西、北西、北東のマスを計算するロジックを作成する。
`BDIJLNOSYZ`の図形は、任意のマス目を`a, b, c, d`と定義し、`a`を原点`a→b`を東方向への単位ベクトルとして正規化する。
`c, d`は、`a, b`および`a→b`のベクトルをもとに相対的な位置関係を正規化する。
そうすると、任意の位置を`a`に特定し、`a→b`のベクトルと残りの三点が`b, c, d`を計算することができる。

入力値の一点を任意に`a`と仮定し、残りの3点が`b, c, d`に一致する図形が存在するかを調査する。

ちなみに、ハニカム構造を二次元配列として扱うのは難しかったので諦めた。

# コード

```hexom.rb
require 'test/unit'

DIRECTIONS = %i(e se sw w nw ne)

# NOTE: a〜yのマス目を0〜22の数値として扱う
class Integer
  # NOTE: -1 を要素なしのマジックナンバーとして使う
  def unkown?
    self == -1
  end

  def east_edge?
    %w(e i n r w).map(&:to_idx).include? self
  end

  def far_east_edge?
    %w(e n w).map(&:to_idx).include? self
  end

  def west_edge?
    %w(a f j o s).map(&:to_idx).include? self
  end

  def far_west_edge?
    %w(a j s).map(&:to_idx).include? self
  end

  def north_edge?
    %w(a b c d e).map(&:to_idx).include? self
  end

  def south_edge?
    %w(s t u v w).map(&:to_idx).include? self
  end

  # NOTE: 東、南東、南西、西、北西、北東の要素番号を返す
　　　　# NOTE: 要素が列の東の端の場合、それより東の要素はない
  def e
    return -1 if unkown?
    return -1 if east_edge?
    self + 1
  end

　　　　# NOTE: 要素が東の端から凸状態の場合、それより南東の要素はない
  def se
    return -1 if unkown?
    return -1 if far_east_edge?
    return -1 if south_edge?
    self + 5
  end

  def sw
    return -1 if unkown?
    return -1 if far_west_edge?
    return -1 if south_edge?
    self + 4
  end

  def w
    return -1 if unkown?
    return -1 if west_edge?
    self - 1
  end

  def nw
    return -1 if unkown?
    return -1 if far_west_edge?
    return -1 if north_edge?
    self - 5
  end

  def ne
    return -1 if unkown?
    return -1 if far_east_edge?
    return -1 if north_edge?
    self - 4
  end
end

class String
  # NOTE: to_i を override すると unit/test が落っこちる
  def to_idx
    [*"a".."w"].find_index self
  end
end

# NOTE:
# 1. 各方角の要素を算出するmethodを呼び出す
# 2. 方角を回転させて呼び出すmethodを切り替える
# 3. method chainしたい
# を同時に満たすため、Symbol classを拡張しています
class Symbol
  # NOTE: 方角を時計回りに１つ回す
  def rotate
    next_direction_idx = DIRECTIONS.find_index(self) + 1
    DIRECTIONS[next_direction_idx % 6]
  end

  # NOTE: 方角を反時計回りに１つ回す
  def reverse
    next_direction_idx = DIRECTIONS.find_index(self) - 1
    DIRECTIONS[next_direction_idx % 6]
  end
end

# NOTE: 各パターンにマッチするかを探索
# `a`と`a→b`のベクトルから b, c, dを生成し、入力値を完全に含んでいるかを検査する
def i? ints
  ints.any? do |a|
    DIRECTIONS.any? do |direction|
      b = a.send(direction)
      c = b.send(direction)
      d = c.send(direction)
      true if [b, c, d].all?{ |x| ints.include? x }
    end
  end
end

def b? ints
  ints.any? do |a|
    DIRECTIONS.any? do |direction|
      b = a.send(direction)
      c = b.send(direction)
      d = b.send(direction.reverse)
      true if [b, c, d].all?{ |x| ints.include? x }
    end
  end
end

def d? ints
  ints.any? do |a|
    DIRECTIONS.any? do |direction|
      b = a.send(direction)
      c = b.send(direction)
      d = b.send(direction.rotate)
      true if [b, c, d].all?{ |x| ints.include? x }
    end
  end
end

def j? ints
  ints.any? do |a|
    DIRECTIONS.any? do |direction|
      b = a.send(direction)
      c = b.send(direction)
      d = c.send(direction.rotate)
      true if [b, c, d].all?{ |x| ints.include? x }
    end
  end
end

def l? ints
  ints.any? do |a|
    DIRECTIONS.any? do |direction|
      b = a.send(direction)
      c = b.send(direction)
      d = c.send(direction.reverse)
      true if [b, c, d].all?{ |x| ints.include? x }
    end
  end
end

def n? ints
  ints.any? do |a|
    DIRECTIONS.any? do |direction|
      b = a.send(direction)
      c = b.send(direction.rotate)
      d = c.send(direction.rotate.rotate)
      true if [b, c, d].all?{ |x| ints.include? x }
    end
  end
end

def o? ints
  ints.any? do |a|
    DIRECTIONS.any? do |direction|
      b = a.send(direction)
      c = b.send(direction.rotate.rotate)
      d = c.send(direction.rotate.rotate.rotate)
      true if [b, c, d].all?{ |x| ints.include? x }
    end
  end
end

def s? ints
  ints.any? do |a|
    DIRECTIONS.any? do |direction|
      b = a.send(direction)
      c = b.send(direction.reverse)
      d = c.send(direction)
      true if [b, c, d].all?{ |x| ints.include? x }
    end
  end
end

def y? ints
  ints.any? do |a|
    DIRECTIONS.any? do |direction|
      b = a.send(direction)
      c = a.send(direction.rotate.rotate)
      d = a.send(direction.reverse.reverse)
      true if [b, c, d].all?{ |x| ints.include? x }
    end
  end
end

def z? ints
  ints.any? do |a|
    DIRECTIONS.any? do |direction|
      b = a.send(direction)
      c = b.send(direction.rotate)
      d = c.send(direction)
      true if [b, c, d].all?{ |x| ints.include? x }
    end
  end
end

# NOTE: メイン関数
def hexom_pattern input
  splited = input.split("").sort.uniq

  return "-" if splited.length < 4

  ints = splited.map(&:to_idx)

  # NOTE： 全種類の図形との該当を検査する場合はここをアンコメント
  # %w(b d i j l n o s y z).each do |s|
  %w(b d i).each do |s|
    return s.upcase if send((s + "?").to_sym, ints)
  end
  return "-"
end

class TestHexom < Test::Unit::TestCase
  def test_hexom_pattern
    assert_equal hexom_pattern("glmq"), "B"
    assert_equal hexom_pattern("fhoq"), "-"
    assert_equal hexom_pattern("lmpr"), "-"
    assert_equal hexom_pattern("glmp"), "-"
    assert_equal hexom_pattern("dhkl"), "-"
    assert_equal hexom_pattern("glpq"), "D"
    assert_equal hexom_pattern("hlmq"), "-"
    assert_equal hexom_pattern("eimq"), "I"
    assert_equal hexom_pattern("cglp"), "-"
    assert_equal hexom_pattern("chlq"), "-"
    assert_equal hexom_pattern("glqr"), "-"
    assert_equal hexom_pattern("cdef"), "-"
    assert_equal hexom_pattern("hijk"), "-"
    assert_equal hexom_pattern("kpqu"), "B"
    assert_equal hexom_pattern("hklm"), "B"
    assert_equal hexom_pattern("mqrw"), "B"
    assert_equal hexom_pattern("nrvw"), "B"
    assert_equal hexom_pattern("abfj"), "B"
    assert_equal hexom_pattern("abcf"), "B"
    assert_equal hexom_pattern("mrvw"), "D"
    assert_equal hexom_pattern("ptuv"), "D"
    assert_equal hexom_pattern("lmnr"), "D"
    assert_equal hexom_pattern("hklp"), "D"
    assert_equal hexom_pattern("himr"), "D"
    assert_equal hexom_pattern("dhil"), "D"
    assert_equal hexom_pattern("hlpt"), "I"
    assert_equal hexom_pattern("stuv"), "I"
    assert_equal hexom_pattern("bglq"), "I"
    assert_equal hexom_pattern("glmn"), "-"
    assert_equal hexom_pattern("fghm"), "-"
    assert_equal hexom_pattern("cdgk"), "-"
    assert_equal hexom_pattern("lpst"), "-"
    assert_equal hexom_pattern("imrw"), "-"
    assert_equal hexom_pattern("dinr"), "-"
    assert_equal hexom_pattern("cdin"), "-"
    assert_equal hexom_pattern("eghi"), "-"
    assert_equal hexom_pattern("cdeg"), "-"
    assert_equal hexom_pattern("bgko"), "-"
    assert_equal hexom_pattern("eimr"), "-"
    assert_equal hexom_pattern("jotu"), "-"
    assert_equal hexom_pattern("kotu"), "-"
    assert_equal hexom_pattern("lqtu"), "-"
    assert_equal hexom_pattern("cdim"), "-"
    assert_equal hexom_pattern("klot"), "-"
    assert_equal hexom_pattern("kloq"), "-"
    assert_equal hexom_pattern("kmpq"), "-"
    assert_equal hexom_pattern("qrvw"), "-"
    assert_equal hexom_pattern("mnqr"), "-"
    assert_equal hexom_pattern("kopt"), "-"
    assert_equal hexom_pattern("mnpq"), "-"
    assert_equal hexom_pattern("bfko"), "-"
    assert_equal hexom_pattern("chin"), "-"
    assert_equal hexom_pattern("hmnq"), "-"
    assert_equal hexom_pattern("nqrw"), "-"
    assert_equal hexom_pattern("bchi"), "-"
    assert_equal hexom_pattern("inrw"), "-"
    assert_equal hexom_pattern("cfgj"), "-"
    assert_equal hexom_pattern("jnpv"), "-"
    assert_equal hexom_pattern("flmp"), "-"
    assert_equal hexom_pattern("adpw"), "-"
    assert_equal hexom_pattern("eilr"), "-"
    assert_equal hexom_pattern("bejv"), "-"
    assert_equal hexom_pattern("enot"), "-"
    assert_equal hexom_pattern("fghq"), "-"
    assert_equal hexom_pattern("cjms"), "-"
    assert_equal hexom_pattern("elov"), "-"
    assert_equal hexom_pattern("chlm"), "D"
    assert_equal hexom_pattern("acop"), "-"
    assert_equal hexom_pattern("finr"), "-"
    assert_equal hexom_pattern("qstu"), "-"
    assert_equal hexom_pattern("abdq"), "-"
    assert_equal hexom_pattern("jkln"), "-"
    assert_equal hexom_pattern("fjkn"), "-"
    assert_equal hexom_pattern("ijmn"), "-"
    assert_equal hexom_pattern("flqr"), "-"
  end
end
```

# 実行結果

```
% ruby hexom.rb
Loaded suite hexom
Started
.

Finished in 0.416804 seconds.
--------------------------------------------------------------------------------------
1 tests, 75 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
--------------------------------------------------------------------------------------
2.40 tests/s, 179.94 assertions/s
```