オフラインリアルタイムどう書くE27の参考問題の実装例(python)

[**オフラインリアルタイムどう書く E27**]( https://yhpg.doorkeeper.jp/events/79705 )の参考問題「**灯りと鏡**」の実装例を、python3 で。
※ 手元の環境は Python 3.5.2 :: Anaconda 4.2.0 (x86_64)

問題 : http://nabetani.sakura.ne.jp/hena/orde27ligmir/
実装リンク集 : https://qiita.com/Nabetani/items/0b2f459ec128c89948f4

[ruby 版]( https://qiita.com/Nabetani/items/63089dabbbb691b7da48 )を python3 に移植した。

```python3:python3
import json

N=5

class State:
  def __init__( self, pos, dir ):
    self._pos = pos
    self._dir = dir

  def pos(self):
    return self._pos[:]

  def advance(self):
    for i in range(2):
      self._pos[i] += self._dir[i]

  def inbound(self):
    return self._pos[0] in range(N) and self._pos[1] in range(N)
  
  def x(self):
    return self._pos[0]

  def y(self):
    return self._pos[1]
  
  def reflect(self,p):
    self._dir = [ p*self._dir[1], p*self._dir[0] ]

def impl(map, s):
  visited=[s.pos()]
  for _ in range(2*N*N):
    s.advance()
    if not s.inbound():
      break
    if map[s.y()][s.x()]=="x":
      break
    visited.append( s.pos() )
    cell = map[s.y()][s.x()]
    if cell=="0":
      s.reflect(1)
    if cell=="1":
      s.reflect(-1)
  return visited

def solve(src):
  xy = list(reversed(divmod( src.find("Y"), 6 )))
  map = src.split("/")
  visited = impl( map, State(xy,[0,-1]) )
  return "".join(sorted(list(set([
    chr(xy[0]+xy[1]*N+ord('a')) for xy in visited
  ] ) ) ) )

with open('data.json') as f:
  data = json.load(f)
  for r in data["test_data"]:
    actual = solve(r["src"])
    ok = actual == r["expected"]
    print( " ".join( [
      str(r["number"]), 
      ("ok" if ok else "**NG**" ), 
      r["src"], 
      actual, 
      r["expected"]] ) )
```

python ではソースコードの末尾に JSON を埋める簡単な方法がないようなので、別ファイルにした。

```json:data.json
{"event_id":"E27","event_url":"https://yhpg.doorkeeper.jp/events/79705","test_data":[
  {"number":0,"src":"x...x/.1.0./..0../.Y.../0..x.","expected":"ghilnqs"},
  {"number":1,"src":"..Y../...../...../...../.....","expected":"c"},
  {"number":2,"src":"..x../..Y../...../...../.....","expected":"h"},
  {"number":3,"src":"..Y.x/..1x0/11.../....0/1..1.","expected":"c"},
  {"number":4,"src":"....1/....Y/...../...../.....","expected":"ej"},
  {"number":5,"src":".10../x.1../x.1x./.Y.1./...0.","expected":"bcghlq"},
  {"number":6,"src":"0.x10/00..x/x0x.0/....0/...Y1","expected":"deinsx"},
  {"number":7,"src":"1.01./01Y.1/..1.1/..10./0.0..","expected":"abcfgh"},
  {"number":8,"src":"x..../x1x../0...0/....Y/.1..0","expected":"klmnot"},
  {"number":9,"src":"...../..10./.1Y1./.01../.....","expected":"hilmnqr"},
  {"number":10,"src":"...../..10./x.11./...../..Y..","expected":"hilmnrw"},
  {"number":11,"src":"...../x.10x/...../x.Y1x/.....","expected":"himnqrs"},
  {"number":12,"src":"..010/...Y1/..0../0.x../.....","expected":"defghij"},
  {"number":13,"src":"1.0../...../.0x../Y.1x./..1..","expected":"abcfhkp"},
  {"number":14,"src":"...../101../0.0../..Y../.....","expected":"fgklmqrv"},
  {"number":15,"src":"1.0../00.../.x..0/0.Y1./...10","expected":"abcfghmr"},
  {"number":16,"src":"x101./1..../.Y.x./..01./.00.1","expected":"bcghlmrs"},
  {"number":17,"src":"x11../x.x../.0.01/..x../...Y.","expected":"bcglmnsx"},
  {"number":18,"src":"..1.0/x0.x./0.0../x...Y/.10.1","expected":"cdehjmnot"},
  {"number":19,"src":"..x.0/.0.../1..0x/1..1./Y.00.","expected":"klmnpqrsu"},
  {"number":20,"src":"0.1.0/.0.xY/0...0/01..1/x00.x","expected":"cdehjmrwx"},
  {"number":21,"src":"...0./.0.0./..101/...10/..01Y","expected":"mnpqrstwxy"},
  {"number":22,"src":"10..0/.Y.0./0..1./....x/000..","expected":"abfghiklmn"},
  {"number":23,"src":"10..1/...../.1010/110.1/x..Yx","expected":"lmnopqrstx"},
  {"number":24,"src":"110../....1/x1..x/0.0.0/....Y","expected":"bcghlmrsty"},
  {"number":25,"src":"x.101/1..../..001/010Yx/..1.1","expected":"cdehijmnos"},
  {"number":26,"src":"x.111/x10../...0./00.1x/x.Y.1","expected":"ghklmnqrsw"},
  {"number":27,"src":"11.../....0/11..1/1.1../.Y..1","expected":"fghijlmnoqv"},
  {"number":28,"src":"...x1/.1.0./11.1./.01../Y..x.","expected":"cghiklmnpqru"},
  {"number":29,"src":".0.../110x./11..0/01.x./..Y.x","expected":"ghklmnopqrtw"},
  {"number":30,"src":".01.0/.110x/0...0/.01Y./x.1x.","expected":"cdeghilmnqrs"},
  {"number":31,"src":".1100/..1.0/1.11Y/0..1./.0..0","expected":"hijklmnopqrs"},
  {"number":32,"src":"1..00/..11./.100./1..Y1/.....","expected":"abcdfhikmnps"},
  {"number":33,"src":"1.0../.11x0/.00.x/Y.10./.10x0","expected":"abcfghklmpqr"},
  {"number":34,"src":"11110/11.../.x.../.0111/0.Y0.","expected":"deijnorstwxy"},
  {"number":35,"src":"...1./.1.0x/10..0/0Y.11/.0.x0","expected":"ghiklmnopqrst"},
  {"number":36,"src":"...10/x111./0x.11/.0.../0.0Y.","expected":"dehijmnorswxy"},
  {"number":37,"src":".1x../.x1.0/0x.x./x11.1/x0Y.1","expected":"hijmoqrstvwxy"},
  {"number":38,"src":"x.x../x110./1.1.0/0.Y.1/0.00x","expected":"hiklmnopqrstx"},
  {"number":39,"src":"...0./11.00/10..x/..0.1/Y0.10","expected":"ghiklmnpqsuvwx"},
  {"number":40,"src":".110./....0/x..../.0001/11.Y.","expected":"cdfghijmnorstx"},
  {"number":41,"src":"1.00./....1/.1.../0...0/0..1Y","expected":"abcfhkmpqrstwy"},
  {"number":42,"src":".1.01/..x../..100/..Y../...01","expected":"bcdgilmnoqrstvxy"},
  {"number":43,"src":"1...0/Y..../...../...../0...1","expected":"abcdefjkoptuvwxy"},
  {"number":44,"src":"x1..0/1..0./.Yx../0...1/.0.1.","expected":"bcdefghijklnopqrstvwx"},
  {"number":45,"src":"1...0/.1.0./..1../..01./Y0..1","expected":"abcdefghijklmnopqrstuvwxy"}]}
```

python 力はあまり高くないし、なれない class を使っているので適切なコードになっているかどうかあまり自信がない。

`"".join(sorted(list(set(略))))` の辺りが美しくない気もするし、仕方ない気もする。

実装方針は ruby版とほぼ同じ。
終了条件も `for _ in range(50):` といういい加減なもの。

とはいえ全く同じでは面白くないので、座標を複素数じゃなくて整数の配列にした。

python の複素数が `1+2j` のように j を使うことを知って驚いた。

あと。
この記事を書くにあたって、「[ruby でこう書くのは、python ならこう書く、のメモ]( https://qiita.com/Nabetani/items/50b0f6533a15d8fb2ae5 )」をちょっと更新した。

イベントの本番( see https://yhpg.doorkeeper.jp/events/79705 )では python の人は最近少ないけど、python での参加も大歓迎です。