第29回オフラインリアルタイムどう書く「サイコロの展開図」の ruby による実装


問題 : http://nabetani.sakura.ne.jp/hena/ord29devdice/
解答リンク集 : http://qiita.com/Nabetani/items/4c21127f07bfa68fa1f8
イベント : https://yhpg.doorkeeper.jp/events/20844

で。

当日お見せした実装。

PATTERNS = %w(
  12S/453 1T6/D45 146/53D 15S/3D4
  215/T64 6T1/54D 2S5/41T 3D4/15S
  512/46T 41T/2S5 453/12S 53D/146
).flat_map{ |x| [x,x.reverse] }

def solve( src )
  re=/#{src.tr("xyzw", "....")}/
  m=PATTERNS.select{ |x| re===x }
  case m.size
  when 0; 
    "none"
  when 1
    src.chars.zip( m[0].chars )
      .select{ |a,b| a!=b }
      .map{ |c| c.join("=") }
      .join(",")
  else
    "many"
  end
end

DATA.map{ |line|
  id, src, expected = line.split(/\s+/)
  actual = solve(src)
  (actual == expected).tap do |ok|
    puts '***NG*** : %s %s->%s ( %s )' % [id, src, actual, expected] unless ok
  end
}.tap{ |x| puts( "ok:%d / NG:%d" % [ x.count(true), x.count(false) ] ) }

__END__
0 Tx4/5yz x=1,y=S,z=2
1 14S/xyz none
2 1w6/xyz many

で。

展開図は

  • 上段左端に来る目は6通り( 2 と D を同一視して )
  • その隣に来る目は4通り( 2 と D を同一視して )

ということで、全部で 6×4=24 通りしかない。

しかも。
展開図の文字列は reverse しても有効な展開図なので、実際には 12 通り列挙すれば足りてしまう。

というわけで、12通りまたは24通りの展開図をソースコード上に書ききれば勝ったも同然。
1個書くのに1分要しても30分以上余る。

全部の展開図が手に入れば、あとは正規表現で調べれば答えがわかる。
簡単でしょ?

まあ列挙するのはかっこ悪い感じではあるけれども、かっこ悪くても計算が正しければ良いのである。

で。
続いて、列挙した結果をソースに書きたくない場合。
DATA.map{ 以下は同じなので、それより前の部分を:

# 目の図形を90度回す
def rot_face(f)
  key="D2T3S6114455"
  key[ key.index(f)^1 ]
end

# サイコロを90度回す
def rot_dice(s)
  [rot_face(s[5]), s[0], (s[1]), 
   "/", 
   rot_face(s[4]), rot_face(s[2]), rot_face(s[6])
  ].join
end

# 展開図の可能なパターンをすべて列挙する
def enum_patterns( pats={}, c="146/53D", &block)
  return if pats[c]
  pats[c]=true
  block.(c)
  enum_patterns(pats, c.reverse, &block)
  enum_patterns(pats, rot_dice(c), &block)
end

#解く
def solve( src )
  re=/#{src.tr("xyzw", "....")}/
  m = enum_for(:enum_patterns).select{ |x| re===x }
  case m.size
  when 0
    "none"
  when 1
    src.chars.zip( m[0].chars )
      .select{ |a,b| a!=b }
      .map{ |c| c.join("=") }
      .join(",")
  else
    "many"
  end
end

というわけで、まじめに回すとだいぶ長くなる。
簡単に説明すると:

  • サイコロの二種類の方法で回しつつ、メモ化再帰で探索。( enum_patterns )
  • サイコロの回し方は二種類。rot_dice と、reverse

という感じ。

rot_face では、排他的論理和( ^ )を使ってみた。ビット演算が明らかに要求されている文脈以外ではなかなか出番のない排他的論理和だけど、たぶん、ここは使いどころだと思う。

enum_patterns はデフォルト引数の悪用っぽい感じがする。どうかなぁ。