Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PyconJP2018 09_18 小栗潤一 Pythonで「お絵描きパズル」を解いてみた

220889d1a098977a6dabbeffd3e6f3a5?s=47 J-Ogu
September 18, 2018

PyconJP2018 09_18 小栗潤一 Pythonで「お絵描きパズル」を解いてみた

発表で使用した資料をアップします。

220889d1a098977a6dabbeffd3e6f3a5?s=128

J-Ogu

September 18, 2018
Tweet

Transcript

  1. Pythonで 「お絵描きパズル」 を 解いてみた PyCon JP 2018 09_18 小栗 潤一

  2. 自己紹介

  3. 小栗 潤一 ・ 広島在住 ・ 大学時代は福岡で デザインの勉強 ・ 広島のチラシ制作会社勤務 3年間デザイナーとして働き

    9年間社内業務改善のプログラマー 自己紹介
  4. 自己紹介 私とPythonについて ・ 勉強を初めて約2年 ・ 広島のもくもく会 「すごい広島」 「すごい広島 with Python」

  5. 今日の議題

  6. Python初心者の私が約3ヶ月かけて ロジックを考えPythonのコードにした話^^ そのコードを使って 某懸賞雑誌一冊分 120問に挑戦しました どこまで解けたのかお楽しみに!!

  7. 今日の議題 ・お絵描きパズルのルール ・パズルを解くのにPythonを使用した利点 ・ロジックを考え、 コードにする話 ・Jupyter Notebookを使用したデモ ・120問中どこまで解けたのか? ・まとめ

  8. お絵描きパズル とは

  9. お絵描きパズルとは 数字をヒントに マスを塗っていき 現れたイラストを 答えるゲーム 1990年代に 流行

  10. お絵描きパズルとは お絵かきロジック (世界文化社) ピクロス (任天堂) ののぐらむ イラストロジック 名称が違うだけで 同じルール

  11. お絵描きパズルとは 用語の説明 白のマス ・ 処理が未確定の空白のマス ・ □□□で表現 ・ 配列内では[0, 0,

    0] 黒のマス ・ 塗りが確定した塗りつぶしたマス ・ ▪▪▪で表現 ・ 配列内では[1, 1, 1] 塗り指示 ・ 問題の左・上辺にあるヒントの数字 ×のマス ・ 塗られないことが確定したマス ・ □□□で表現 ・ 配列内では[-1, -1, -1] ×××
  12. 「お絵描きパズルを解いてみた」 初心者が考えた Pythonの利点

  13. 「お絵描きパズルを解いてみた」 Pythonの利点 Pandas、 Numpyなどの便利なライブラリ [0,0,0,0,0,0,0,0,0,0] [10] [0,0,0,0,0,0,0,0,0,0] [3,2,2] DataFrame Numpyの配列計算

    Series
  14. それを踏まえて もう一度ご覧ください

  15. 「お絵描きパズルを解いてみた」 Pythonの利点 Pandas、 Numpyなどの便利なライブラリ [1, 1, 1, 1, 1, 1,

    1, 1, 1, 1] [10] [1, 1, 1,-1, 1, 1,-1,-1, 1, 1] [3,2,2] DataFrame Numpyの配列計算 Series
  16. 「お絵描きパズルを解いてみた」 Pythonの利点 Jupyter Notebookを使った開発 ・ デモ環境 Jupyter Notebook ipywidgets RISE

    (拡張機能)
  17. Jupyter Notebookを使って 問題を解かせてみましょう or デバッグ環境の紹介

  18. ・Pandas,Numpyを使って 簡単な配列計算で解けそう ・ 動きのあるデバック環境が 簡単に作れる 私の考えたPythonを使う利点

  19. お絵描きパズルのルール ロジックの話

  20. お絵描きロジックの基本的なルール ・数字と同じマスを塗りつぶす ・数字が複数ある場合は必ず1マス以上空ける 3 2,3 2,2 3 3

  21. アルゴリズム (ロジック) を考えてみましょう

  22. ロジックを考える まずは手動で解いてみる 普段何気なく問題を解く際に 頭の中で行なっている アルゴリズムを書き出してみる

  23. まずは問題を解いてみよう 処理1 確定マスの塗りつぶし 処理5 端から確定マスを見つける 処理2 端が確定した際の処理 処理6 指示の最大数と同じ連続した塗り 処理3

    届かないマスの確定処理 処理7 端から指示 +1のマスが塗られていたら 処理9 端から 「1」 と 「2」 の連続した塗り指示の確定処理 処理4 「×」 のマスで分割して処理1〜3を実行 処理8 端から 「1」 の連続した塗り指示の確定処理 処理10 連続された塗りから対象の指示数字を特定する ロジックを書き出してみる
  24. ロジックの構造と全体の流れ # 変数の指定 n_list = np.array([0, 0, 0, 0, 0,

    0, 0, 0, 0, 0]) n_vals = [3,3] # 関数の指定 def nuri_syori(n_list, n_vals): ck_n_list = copy.copy(n_list) ck_n_list[0] = 2 while not np.allclose(n_list, ck_n_list) and 0 in n_list: ck_n_list = copy.copy(n_list) for i in range(2): # リストの反転 if i == 1: n_list = n_list[::-1] n_vals = n_vals[::-1] # 処理1〜10 logic_shori_1 () 〜 logic_shori_10 () # リストの反転(戻す処理) if i == 1: n_list = n_list[::-1] n_vals = n_vals[::-1]
  25. ロジックで 某懸賞雑誌 120問に挑戦します どこまで解けたのでしょうか^^ ロジックの解説から始めましょう

  26. 6,2 処理1 確定マスの塗りつぶし 解き方

  27. 処理1 確定マスの塗りつぶし 解き方 6,2 6,2 6,2 6,2 左から詰めて6の配置 右から詰めて6の配置 左詰め時の一番右のマスから

    右詰め時の一番左マスまでは確定 6,2 6,2 6,2 左から詰めて2の配置 右から詰めて2の配置 左詰め時の一番右のマスから 右詰め時の一番左マスまでは確定
  28. 解き方 n_list = np.array([0, 0, 0, 0, 0, 0, 0,

    0, 0, 0]) n_vals = [6,2] # 隙間の数を埋める処理リファクト後の処理 for n,val in enumerate(n_vals): af = sum(n_vals[n:]) + len(n_vals[n:]) - 1 bf = sum(n_vals[:n+1]) + len(n_vals[:n+1]) - 1 if not list(n_list[-af:bf]):continue n_list[-af:bf] = 1 ## n_list = [0,1,1,1,1,1,0,0,1,0] 左から詰めて6の配置 6,2 右から詰めて6の配置 6,2 左詰め時の一番右のマスから 右詰め時の一番左マスまでは確定 6,2
  29. 処理2 端が確定した際の処理 解き方 3,2 3,2 × 3,2 最初は確定できるマスはありません 行 ・

    列を進める際に確定マスができた 塗りマスと直後の 「×」 確定ができる
  30. 解き方 n_list = np.array([1, 0, 0, 0, 0, 0, 0,

    0, 0, 0]) n_vals = [3,2] # 前方から処理 if n_list[0] == 1.0: n_list[0:n_vals[0]] = 1 n_list[n_vals[0]:n_vals[0]+1] = -1 # 後方から処理 if n_list[-1] == 1.0: n_list[-n_vals[-1]:len(n_list)] = 1 n_list[-n_vals[-1] -1:-n_vals[-1]] = -1 ## n_list = [1,1,1,-1,0,0,0,0,0,0] 3,2 × 3,2 行・列を進める際に確定マスができた 塗りマスと直後の 「×」 確定ができる
  31. 処理3 届かないマスの確定処理 解き方 3 3 × × × × ×

    3 3 左右に3マス届く可能性のマス 届かないマスは塗られる可能性がないことが確定
  32. 解き方 n_list = np.array([0, 0, 0, 1, 0, 0, 0,

    0, 0, 0]) n_vals = [3] # 指示が一つ以上あるものは処理中断 if not len(n_vals) == 1: return n_list leftNumm = int(np.where(n_list == 1)[0][0])+int(n_vals[0]) if leftNumm < len(n_list): n_list[leftNumm:len(n_list)] = -1 # 反転してもう一度 n_list = n_list[::-1] leftNumm = int(np.where(n_list == 1)[0][0])+int(n_vals[0]) if leftNumm < len(n_list): n_list[leftNumm:len(n_list)] = -1 # 反転してもとに戻す n_list = n_list[::-1] ## n_list = [-1,0,0,1,0,0,-1,-1,-1,-1] 3 × × × × × 3 3 左右に3マス届く可能性のマス 届かないマスは塗られる 可能性がないことが確定
  33. 処理1・2・3は最初に 実装したロジック 難易度★★☆☆☆ 28問中約8割の問題が 解けるようになった^^v

  34. ロジックの言語化 Try & Err の繰り返し 処理1 確定マスの塗りつぶし 処理5 端から確定マスを見つける 処理2

    端が確定した際の処理 処理6 指示の最大数と同じ連続した塗り 処理3 届かないマスの確定処理 処理7 端から指示 +1のマスが塗られていたら 処理9 端から 「1」 と 「2」 の連続した塗り指示の確定処理 処理4 「×」 のマスで分割して処理1〜3を実行 処理8 端から 「1」 の連続した塗り指示の確定処理 処理10 連続された塗りから対象の指示数字を特定する
  35. ロジックの言語化 Try & Err の繰り返し 処理1 確定マスの塗りつぶし 処理5 端から確定マスを見つける 処理2

    端が確定した際の処理 処理6 指示の最大数と同じ連続した塗り 処理3 届かないマスの確定処理 処理7 端から指示 +1のマスが塗られていたら 処理9 端から 「1」 と 「2」 の連続した塗り指示の確定処理 処理4 「×」 のマスで分割して処理1〜3を実行 処理8 端から 「1」 の連続した塗り指示の確定処理 処理10 連続された塗りから対象の指示数字を特定する 一部のロジックは Wikipediaに載ってる為、 本日は解説しません
  36. まずは問題を解いてみよう 全てのロジックを まとめた資料 興味のある方は こちらから http://bit.ly/2PEDues

  37. ロジックの言語化 Try & Err の繰り返し 処理1 確定マスの塗りつぶし 処理5 端から確定マスを見つける 処理2

    端が確定した際の処理 処理6 指示の最大数と同じ連続した塗り 処理3 届かないマスの確定処理 処理7 端から指示 +1のマスが塗られていたら 処理9 端から 「1」 と 「2」 の連続した塗り指示の確定処理 処理4 「×」 のマスで分割して処理1〜3を実行 処理8 端から 「1」 の連続した塗り指示の確定処理 処理10 連続された塗りから対象の指示数字を特定する
  38. wikiに載ってない オリジナルロジック の解説

  39. 処理7 端から指示 +1のマスが塗られていたら 5,1 × 5,1 5,1 × 5,1 左端から

    「5」 を塗ったと仮定すると 「5」 の塗りの可能性は 左端のマスは塗られないことが確定
  40. 処理8 端から 「1」 の連続した塗り指示の確定処理 1,1,1,2 × × × × 1,1,1,2

    × × × 1,1,1,2 仮に 「1」 が左から詰めて塗られていたら この範囲にある塗りは必ず 「1」 になる ここまではWikipediaに載ってるロジック
  41. 処理9 端から 「1」 と 「2」 の連続した塗り指示の確定処理 1,2,1,1 × 1,2,1,1 ×

    1,2,1,1 × × × × 1,2,1,1 処理8は 「1の連続した」 という条件があった 「1と2の連続した」 という条件に変える この範囲にある[▪,□,▪]の[□]は×が確定
  42. ロジックの言語化 難易度★★★★☆まで解ける(一部を除いて) 処理1 確定マスの塗りつぶし 処理5 端から確定マスを見つける 処理2 端が確定した際の処理 処理6 指示の最大数と同じ連続した塗り

    処理3 届かないマスの確定処理 処理7 端から指示 +1のマスが塗られていたら 処理9 端から 「1」 と 「2」 の連続した塗り指示の確定処理 処理4 「×」 のマスで分割して処理1〜3を実行 処理8 端から 「1」 の連続した塗り指示の確定処理 処理10 連続された塗りから対象の指示数字を特定する どれ一つとして難しい 処理は行なっていません PandasのDataFrameを Seriesで取得して Numpyのndarrayに変換して 配列計算しているだけ
  43. それでも解けない問題

  44. ・確定するマスがなくなる ・仮説処理で進めるしかない それでも解けない問題がでてくる

  45. それでも解けない問題がでてくる 総当たり処理 ・確定するマスがなくなる ・仮説処理で進めるしかない

  46. どれくらいの計算量になるのか? それでも解けない問題がでてくる × × × × × × 30 2,2,1,3,2,1,1

    2,2,1,3,2,1,1 30 13 13**8= 815,730,721 ??????
  47. それでも解けない問題がでてくる 総当たり処理 ・確定するマスがなくなる ・仮説処理で進めるしかない 計算量の少ない仮説

  48. ではどう進める?

  49. 上下左右のうち 計算量の少ない1辺の 仮説を立てるだけ

  50. 仮説処理 1辺だけの総当たり処理 itertoolsを使って 4辺のパターン数を計算 1行目 :4パターン 15行目 :11パターン 1列目 :19パターン

    20列目 :120パターン
  51. 仮説処理 1行目:4パターン そのまま最後まで解ける 確定マスがなくなる (さらなる仮説処理へ) 矛盾が発生 一番計算処理が少ない 辺から進める

  52. 仮説処理 0個 1個 複数 ありえない 確定パターン パターンを 記憶して 別の辺を進める 確定マスがなくなる

    パターン数が重要
  53. 仮説処理 2つ目の辺も 複数残った場合は 1つ目と2つ目の パターンを 総当たりチェック 必ず確定パターンが見つかる 1つ目の辺の残った パターンを記憶して 2つ目の辺を仮説処理

  54. 仮説処理を 追加することで 懸賞雑誌に載っている 問題がほぼ全て解けた 難易度★★★★★

  55. 懸賞雑誌一冊分 120問中カラーロジックを除く 116問全て解けた ^^

  56. 本日デモに使用した 問題もPythonで自作 イラストから問題作成 作成した問題を解く

  57. まとめ

  58. まとめ ・Pythonには便利なライブラリが豊富にあり 「お絵描きパズル」 のような配列計算で答えが 出せるゲームと相性が良い ・ ロジックを考えるのは難しいが コード自体はDataFrameをSeriesで取得して Numpyのndarrayで計算している簡単なコード 「お絵描きパズル」

    「数独」 とも相性がいい ぜひ自分でトライしてみてください ^ ^ v