数独の問題作成をpythonで作りたい vol.4

  • このエントリーをはてなブックマークに追加

今通っているTech::Expert心斎橋校が、なんばに移転するようですね。綺麗な建物、綺麗な環境で勉強できるのはとても素晴らしいですね。自分は最終課題を新教室で発表します。なんば校で初発表になると思うので、素晴らしい発表にしたいですね。

さて引き続き数独を作っていきます。

今日は各種役所に行っていたのであまり勉強できませんでした。なので、短い時間でできることに焦点を絞って勉強しました。

今日やったこと

今日やったのは大きく3つです。

  • リファクタリング
  • 空欄を挿入
  • Githubの整理

リファクタリング

前回は時間の関係で中途半端なところで終えていましたね!

今日はそのリファクタリングから始めました。

from enum import Enum
import numpy as np
import random
"""
                 _       _          
                | |     | |         
   ___ _   _  __| | ___ | | ___   _ 
  / __| | | |/ _` |/ _ \| |/ / | | |
  \__ \ |_| | (_| | (_) |   <| |_| |
  |___/\__,_|\__,_|\___/|_|\_/\__,_|


"""     
class Sudoku():
    # 定数
    SHUFFLE_ABC   = 1
    SHUFFLE_DEF   = 2
    SHUFFLE_GHI   = 3
    SHUFFLE_123   = 4
    SHUFFLE_456   = 5
    SHUFFLE_789   = 6
    SHUFFLE_ROWS  = 7
    SHUFFLE_COLS  = 8
    SHUFFLE_NUM   = 9
    BLANK_NUM = 40
    
    def __init__(self):
        self.table = []
        self.answer = []
        self.question = []
        self.set_default_table()
        self.set_answer()
        self.set_question(self.answer)

    def set_default_table(self):
        self.table = np.array([
                        [1,4,7,2,5,8,3,6,9],
                        [2,5,8,3,6,9,4,7,1],
                        [3,6,9,4,7,1,5,8,2],
                        [4,7,1,5,8,2,6,9,3],
                        [5,8,2,6,9,3,7,1,4],
                        [6,9,3,7,1,4,8,2,5],
                        [7,1,4,8,2,5,9,3,6],
                        [8,2,5,9,3,6,1,4,7],
                        [9,3,6,1,4,7,2,5,8]
                    ])

    def set_answer(self):
        for i in range(1000):
            self.shuffle(random.randint(1,9),self.table)
        self.answer = self.table

    def set_blank(self,_table):
        blank_num = self.BLANK_NUM
        boolean_data = np.array([True]*81)
        boolean_data[0:blank_num] = False
        random.shuffle(boolean_data)
        filter_data = np.reshape(boolean_data,(9,9))
        for i in range(9):
            for j in range(9):
                if not filter_data[i][j]:
                    _table[i][j] = 0
        return _table

    def set_question(self,_table):
        self.table = self.set_blank(np.copy(_table))
        # 解けるか確認する
        # 解けるまで繰り返す
        self.question = self.table

    def get_answer(self):
        return self.answer

    def get_question(self):
        return self.question

    def display(self,_table):
        print(_table)

    def test_format(self,_table):
        column_sum = np.array([0] * 9)
        for i in range(9):
            row_sum = np.array(_table[i]).sum()
            if row_sum != 45:
                msg="This is not Sudoku format."
            column_sum += np.array(_table[i])
        if np.array([45] * 9).all() != column_sum.all():
            msg="This is not Sudoku format."
        if str(_table.shape) != "(9, 9)":
            msg="This is not Sudoku format."

    def shuffle(self,type,_table):
        def inner_shuffle_function(_table,start,end,is_transpose):
            if is_transpose:
                _table = _table.T
            transposed = _table
            partial = transposed[start:end]
            np.random.shuffle(partial)
            transposed[start:end] = partial
            if is_transpose:
                transposed = transposed.T
            _table = transposed
   
        if type == self.SHUFFLE_ABC:
            inner_shuffle_function(_table,0,3,True)
        elif type == self.SHUFFLE_DEF:
            inner_shuffle_function(_table,3,6,True)
        elif type == self.SHUFFLE_GHI:
            inner_shuffle_function(_table,6,9,True)
        elif type == self.SHUFFLE_123:
            inner_shuffle_function(_table,0,3,False)
        elif type == self.SHUFFLE_456:
            inner_shuffle_function(_table,3,6,False)
        elif type == self.SHUFFLE_789:
            inner_shuffle_function(_table,6,9,False)
        elif type == self.SHUFFLE_ROWS:
            partial = [np.copy(_table[0:3]),np.copy(_table[3:6]),np.copy(_table[6:9])]
            random.shuffle(partial)
            for i in range(3):
                _table[(0+3*i):(3+3*i)] = partial[i]
        elif type == self.SHUFFLE_COLS:
            transposed = _table.T
            partial = [np.copy(transposed[0:3]),np.copy(transposed[3:6]),np.copy(transposed[6:9])]
            random.shuffle(partial)
            for i in range(3):
                transposed[(0+3*i):(3+3*i)] = partial[i]
            _table = transposed.T
        elif type == self.SHUFFLE_NUM:
            array = [1,2,3,4,5,6,7,8,9]
            random.shuffle(array)
            for i in range(9):
                for j in range(9):
                    data = _table[i][j]
                    _table[i][j] = array[data - 1]
        else:
            print("fail")
        self.table = _table

昨日の時点では、クラス変数とインスタンス変数がごっちゃになっていましたね。answerテーブルとかquestionテーブルは個々のインスタンス毎でアクセスしたいので、インスタンス変数としての定義に変えました。

def __init__(self):
    self.table = []
    self.answer = []
    self.question = []

セッターとゲッターを用意した上で、display関数も簡略化しました。短くなりましたね!

シャッフルするところはif文で同じような処理が続くので内部関数として定義するようにしました。

def shuffle(self,type,_table):
    def inner_shuffle_function(_table,start,end,is_transpose):
        if is_transpose:
            _table = _table.T
        transposed = _table
        partial = transposed[start:end]
        np.random.shuffle(partial)
        transposed[start:end] = partial
        if is_transpose:
            transposed = transposed.T
        _table = transposed

当然変数名もxとかyになっていたのを適切な名前に変更しました。

余談ですが、プログラミングでの命名は以下サイトに任せたりしています。

codic : プログラマー・システムエンジニアのためのネーミングツール

https://codic.jp/

Banner Comments

気になった人もいると思いますが、注目のこれです。

"""
                 _       _          
                | |     | |         
   ___ _   _  __| | ___ | | ___   _ 
  / __| | | |/ _` |/ _ \| |/ / | | |
  \__ \ |_| | (_| | (_) |   <| |_| |
  |___/\__,_|\__,_|\___/|_|\_/\__,_|


"""     

コメントで立体的に文字を出しています。

VScodeの拡張機能でBanner Comments + を入れると使えるようになります。

banner comments +

https://marketplace.visualstudio.com/items?itemName=lunarlimbo.banner-comments-plus

ブロックコメントを入れたいときにはすごい便利ですよね!

加えてなんか楽しいです(笑)

興味がある人はぜひ使ってみてください。

空欄を挿入

いやいや今日のメインタスクはこれです。

昨日の成果がこれでしたね。

シャッフル

今日の成果がこれです。

見事ランダムに数字が記入されていない(0で表現)状態を作ることができました。

途中段階で以下のようなboolean型の配列を用意して、元データにマッピングしました。

[False False False False False False False False False False False False
  False False False False False False False False False False False False
  False False False False False False False False False False False False
  False False False False  True  True  True  True  True  True  True  True
   True  True  True  True  True  True  True  True  True  True  True  True
   True  True  True  True  True  True  True  True  True  True  True  True
   True  True  True  True  True  True  True  True  True]

  ↓↓↓↓↓↓ シャッフル ↓↓↓↓↓↓

 [[ True  True  True  True  True  True False  True False]
  [ True  True  True False  True False False False False]
  [ True False False False  True False  True False False]
  [ True False  True False False False False  True False]
  [False  True  True  True False False False  True  True]
  [ True False False False  True  True  True False False]
  [ True  True  True  True  True  True False  True  True]
  [False False  True False  True False  True  True False]
  [False False False False False  True False  True  True]]

まとめ

今日は目標通りに実装できました!

明日は一番難しそうなところ!数独の解答プログラムを考えていきます。

SNSでもご購読できます。

スポンサードリンク

コメント

  1. ひぐ より:

    面白いので毎日見ています!
    昨日の内容かもしれないですが、これで全数独の問題のうち、どれくらいの問題を生成できていることになるのでしょうか。
    あと適当に空欄にしても、常に問題は解けるんでしょうか。

    プログラミング関係ないですが、この辺が少し気になっちゃいました。

    1. Ryuji_tech より:

      >>全数独の問題のうち、どれくらいの問題を生成できていることになるのでしょうか。

      全てのマスが埋められた状態は全数独のうちの全数独が生成可能ですね。
      0を入れた空きマス状態は今は定数で空きマス20で固定しているので、全問題のうち一部しか生成できません。
      20を変更すれば全問題生成可能です。

      >>適当に空欄にしても、常に問題は解けるんでしょうか。

      解けない場合があります。
      次の記事で、数独を解くソルバープログラムを作成します。そのソルバーで解くことができれば妥当な問題となるでしょう。
      また、ソルバーの出来具合でも解ける問題の難しさは左右されます。
      ランダムで空欄を入れているので、ここは仕方ないです。問題作るって難しいですね!

      予測では空きマス20〜30で90%ぐらいは解けると思いマス。
      空きマスが40越えるとほぼ解けなくなると想定しています。

      いつも訪問有難うございます。次のソルバープログラムをお待ちください。

コメントを残す

*