【Python】Pythonでナンプレを解くプログラムを作成する

田中太郎
田中太郎

Pythonを使って、ナンプレ(数独)を解くプログラムを作るよ!

Pythonを使ってナンプレを解く

入力

エクセルファイルに数字を入力する

図1:input.xlsx

出力

ナンプレの回答が標準出力で出ます。

図2:出力データ

コード

"""Sudoku."""
import copy
import pandas as pd


def initialize(data):
    """Initialize."""
    print("Set initial value")

    sel_values = []
    for line in range(1,10):
        for column in range(1,10):
            sel_value = {
                "line": line,
                "column": column,
                "group": 0,
                "num_en": {
                    i: "en" for i in range(1, 10)
                },
                "num": data[line-1][column-1],
            }

            # If num exists in the sel, num_en becomes "dis"
            if sel_value["num"] != 0:
                for i in range(1, 10):
                    sel_value["num_en"][i] = "dis"

            # Set group
            # The definition of goup number is the followings
            # 1 2 3
            # 4 5 6
            # 7 8 9
            for i in range(0,3):
                if (1 <= line <= 3 and 1 + i*3 <= column <= 3 + i*3):
                    sel_value["group"] = 1 + i
                if (4 <= line <= 6 and 1 + i*3 <= column <= 3 + i*3):
                    sel_value["group"] = 4 + i
                if (7 <= line <= 9 and 1 + i*3 <= column <= 3 + i*3):
                    sel_value["group"] = 7 + i

            sel_values.append(copy.deepcopy(sel_value))

    return sel_values


def update_num_en(sel_values):
    """Search and update num_en.
    Input: sel_values
    Output: sel_values"""
    # Search the position of updating num_en
    disables = []
    for sel_value in sel_values:
        if (sel_value["num"] != 0):
            disable = {
                "num": sel_value["num"],
                "line": sel_value["line"],
                "column": sel_value["column"],
                "group": sel_value["group"],
            }
            disables.append(copy.copy(disable))

    # Update num_en using above disables
    for sel_value in sel_values:
        if sel_value["num"] != 0:
            continue

        for disable in disables:
            if (disable["line"] == sel_value["line"] 
                or disable["column"] == sel_value["column"]
                or disable["group"] == sel_value["group"]):
                sel_value["num_en"][disable["num"]] = "dis"

    return sel_values


def update_num(sel_values):
    """Update num using num_en in sel_values
    Input: sel_values
    Output: sel_values
    """
    for sel_value in sel_values:
        # Initialize
        check_flag = 0
        put_val = 0

        # Search the value of putting enable
        for num, status in sel_value["num_en"].items():
            if status == "en":
                check_flag += 1
                put_val = num

        if check_flag != 1:
            continue

        sel_value["num"] = put_val

    for sel_value in sel_values:
        if sel_value["num"] == 0:
            continue

        for i in range(1, 10):
            sel_value["num_en"][i] = "dis"

    return sel_values


def fill_by_category_info(sel_values, category):
    """."""
    # Fill num to the field
    # Count num_en in same category by num_counter.
    # If num_counter is 1, this positino will be filled.
    for category_num in range(1,10):
        num_counter = {k: 0 for k in range(1,10)}

        for sel_value in sel_values:
            if sel_value[category] != category_num:
                continue

            for k, v in sel_value["num_en"].items():
                if v == "en":
                    num_counter[k] += 1

        for put_num in range(1, 10):
            if num_counter[put_num] != 1:
                continue

            for sel_value in sel_values:
                # same category
                if sel_value[category] != category_num:
                    continue

                # The category_num of the category has "en" 
                if sel_value["num_en"][put_num] != "en":
                    continue

                sel_value["num"] = put_num

    return sel_values


def output_sel_value(sel_values):
    """Output_sel_value."""
    for i in range(0,81):
        print(int(sel_values[i]["num"]), end="")
        if i%9 == 8:
            print("\n")


def calculate_sudoku(sel_values, iteration_num):
    """Calculate sudoku.
    Input: sel_values, iteration
        sel_values(list of dict):T.B.D.
        iteration(int): iteration number
    Output: sel_values
        sel_values(list of dict): """
    for iteration in range(0, iteration_num):
        # Update num_en
        sel_values = update_num_en(sel_values)

        # Updage num in sel_values
        sel_values = update_num(sel_values)

        # Update num_en
        sel_values = update_num_en(sel_values)

        # Fill lines
        sel_values = fill_by_category_info(sel_values, "line")

        # Fill_columns
        sel_values = fill_by_category_info(sel_values, "column")

        # Fill boxes
        sel_values = fill_by_category_info(sel_values, "group")

    return sel_values


def main():
    """Main."""
    # Read excel file
    print("Read excel file")
    df = pd.read_excel("input.xlsx",header=None)
    df = df.fillna(0).T
    data = list(df.to_dict(orient="list").values())

    # Set initial value
    # sel_values is list of dict
    # dict = {line, column, group, num_en, num}
    sel_values = initialize(data)

    # Print field num
    print("input data")
    output_sel_value(sel_values)

    # Calculate sudoku
    sel_values = calculate_sudoku(sel_values, 100)

    # Print field num
    print("ouput data")
    output_sel_value(sel_values)


if "__main__" == __name__:
    main()
    print("Success!")

解説

mainの中身だけ説明します。残りは暇なときに追加、修正します。

main

def main():
    """Main."""
    # Read excel file
    print("Read excel file")
    df = pd.read_excel("input.xlsx",header=None)
    df = df.fillna(0).T
    data = list(df.to_dict(orient="list").values())

pandasのread_excelでエクセルファイルを読み込んで、Pythonのリスト型に変換します。色々ごにょごにょしていますが、最終的にdataにリストのリスト型でデータが入っています。dataの中身は以下になります。

data = [
[1行1列の数字,1行2列の数字… ]
[2行1列の数字,2行2列の数字…]

[9行1列の数字,9行2列の数字…]
]

    # Set initial value
    # sel_values is list of dict
    # dict = {line, column, group, num_en, num}
    sel_values = initialize(data)

エクセルファイルから読み込んで作ったdataから、ナンプレを解くための型、sel_valuesを作成します。sel_valuesは辞書のリストの形になっています。辞書の中身は

line: 行番号
column: 列番号
group: グループ番号
num_en: 何の数字が入る可能性があるか
num: 何の数字が入っているか

となっています。

    # Print field num
    print("input data")
    output_sel_value(sel_values)

上記は、sel_valuesに入っている値を出力しているだけです

    # Calculate sudoku
    sel_values = calculate_sudoku(sel_values, 100)

上記は、実際にナンプレの空白部分を埋めていきます。

    # Print field num
    print("ouput data")
    output_sel_value(sel_values)

計算結果を出力します。

コメント

タイトルとURLをコピーしました