👋

claude codeに数独の回答をさせるコードを考えさせてみた

に公開

今回は昨日公開した記事の続編になります。昨日は数独の問題を自動で生成させるコードをclaude codeにて実装させましたが、今回はその問題ファイルを読み込んで計算させるコードを作らせてみました。昨日の記事は以下になりますのでぜひ合わせてご覧ください。

https://zenn.dev/akasan/articles/c4b6817e4de906

早速やってみる!

プロンプト!

今回入力したプロンプトは以下になります。

@problem_1.txt のようなフォーマットで入力される数独の回答を導くコードをPythonでかいて。結果はsolve_<ナンバリング>.txtで

昨日作成していた問題ファイルはproblem_<番号>.txtのフォーマットで作成されるようにしていたので、そのサンプルを@problem_1.txtのようにしてclaude codeに読み込ませて、同じナンバリングで回答を作成させるようにしてみました。

実行結果

まずは生成されたコードは以下になります。

sudoku_solver.py
#!/usr/bin/env python3
import re
import sys
import datetime
import os


def parse_sudoku_file(filename):
    with open(filename, 'r') as f:
        lines = f.readlines()
    
    puzzle_number = None
    difficulty = None
    generated_time = None
    grid = []
    
    for line in lines:
        line = line.strip()
        
        if "Sudoku Puzzle #" in line:
            match = re.search(r'#(\d+)', line)
            if match:
                puzzle_number = match.group(1)
        elif "Difficulty:" in line:
            difficulty = line.split("Difficulty:")[1].strip()
        elif "Generated:" in line:
            generated_time = line.split("Generated:")[1].strip()
        elif "|" in line and not line.startswith("-"):
            row_values = []
            parts = line.split("|")
            for part in parts:
                cells = part.strip().split()
                for cell in cells:
                    if cell == ".":
                        row_values.append(0)
                    else:
                        row_values.append(int(cell))
            grid.append(row_values)
    
    return puzzle_number, difficulty, generated_time, grid


def is_valid(grid, row, col, num):
    for j in range(9):
        if grid[row][j] == num:
            return False
    
    for i in range(9):
        if grid[i][col] == num:
            return False
    
    box_row, box_col = 3 * (row // 3), 3 * (col // 3)
    for i in range(box_row, box_row + 3):
        for j in range(box_col, box_col + 3):
            if grid[i][j] == num:
                return False
    
    return True


def solve_sudoku(grid):
    for i in range(9):
        for j in range(9):
            if grid[i][j] == 0:
                for num in range(1, 10):
                    if is_valid(grid, i, j, num):
                        grid[i][j] = num
                        if solve_sudoku(grid):
                            return True
                        grid[i][j] = 0
                return False
    return True


def format_grid(grid):
    formatted = []
    for i in range(9):
        row = ""
        for j in range(9):
            row += str(grid[i][j]) + " "
            if j == 2 or j == 5:
                row += "| "
        formatted.append(row.strip())
        if i == 2 or i == 5:
            formatted.append("-" * 21)
    return "\n".join(formatted)


def write_solution(puzzle_number, difficulty, generated_time, grid, output_filename):
    with open(output_filename, 'w') as f:
        f.write(f"Sudoku Puzzle #{puzzle_number} - Solution\n")
        f.write(f"Difficulty: {difficulty}\n")
        f.write(f"Generated: {generated_time}\n")
        f.write(f"Solved: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
        f.write("=" * 30 + "\n\n")
        f.write(format_grid(grid))
        f.write("\n")


def create_log(puzzle_number, difficulty, success, log_dir="./claude/log"):
    os.makedirs(log_dir, exist_ok=True)
    
    log_filename = os.path.join(log_dir, f"sudoku_solver_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
    
    with open(log_filename, 'w') as f:
        f.write(f"Sudoku Solver Execution Log\n")
        f.write(f"{'=' * 30}\n")
        f.write(f"Timestamp: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
        f.write(f"Puzzle Number: {puzzle_number}\n")
        f.write(f"Difficulty: {difficulty}\n")
        f.write(f"Status: {'Success' if success else 'Failed'}\n")
        f.write(f"Input File: problem_{puzzle_number}.txt\n")
        f.write(f"Output File: solve_{puzzle_number}.txt\n")
    
    return log_filename


def main():
    if len(sys.argv) > 1:
        input_file = sys.argv[1]
    else:
        input_file = "problem_1.txt"
    
    try:
        puzzle_number, difficulty, generated_time, grid = parse_sudoku_file(input_file)
        
        print(f"Solving Sudoku Puzzle #{puzzle_number}...")
        print(f"Difficulty: {difficulty}")
        
        if solve_sudoku(grid):
            output_file = f"solve_{puzzle_number}.txt"
            write_solution(puzzle_number, difficulty, generated_time, grid, output_file)
            print(f"Solution saved to {output_file}")
            
            log_file = create_log(puzzle_number, difficulty, True)
            print(f"Execution log saved to {log_file}")
            
            return 0
        else:
            print("No solution found!")
            log_file = create_log(puzzle_number, difficulty, False)
            print(f"Execution log saved to {log_file}")
            return 1
            
    except Exception as e:
        print(f"Error: {str(e)}")
        return 1


if __name__ == "__main__":
    sys.exit(main())

実装日てですが、以下で入力の問題をパースします。入力ファイルは合わせて以下に載せているようなフォーマットで取得されますが、余分な情報が記載されているのでパースする必要があります。

def parse_sudoku_file(filename):
    with open(filename, 'r') as f:
        lines = f.readlines()
    
    puzzle_number = None
    difficulty = None
    generated_time = None
    grid = []
    
    for line in lines:
        line = line.strip()
        
        if "Sudoku Puzzle #" in line:
            match = re.search(r'#(\d+)', line)
            if match:
                puzzle_number = match.group(1)
        elif "Difficulty:" in line:
            difficulty = line.split("Difficulty:")[1].strip()
        elif "Generated:" in line:
            generated_time = line.split("Generated:")[1].strip()
        elif "|" in line and not line.startswith("-"):
            row_values = []
            parts = line.split("|")
            for part in parts:
                cells = part.strip().split()
                for cell in cells:
                    if cell == ".":
                        row_values.append(0)
                    else:
                        row_values.append(int(cell))
            grid.append(row_values)
    
    return puzzle_number, difficulty, generated_time, grid
problem_1.txt
Sudoku Puzzle #1
Difficulty: low
Generated: 2025-09-30 20:19:48
==============================

. 8 9 | 3 . 2 | 5 . . 
6 5 . | . . . | 1 2 . 
. . 1 | 6 8 5 | . 3 . 
---------------------
5 . 7 | 1 3 8 | . . 2 
3 6 . | 5 9 . | 8 4 1 
9 . 8 | . . 4 | . 5 3 
---------------------
. . . | 8 5 3 | 6 1 . 
. 7 5 | 9 2 . | . 8 . 
. 3 . | 7 4 1 | 2 9 .

それ以外で言うと、以下が実際に解いている場所になります。かなりゴリ押しというか、ネストされたforを利用して当てはまる数値を探しています。

def solve_sudoku(grid):
    for i in range(9):
        for j in range(9):
            if grid[i][j] == 0:
                for num in range(1, 10):
                    if is_valid(grid, i, j, num):
                        grid[i][j] = num
                        if solve_sudoku(grid):
                            return True
                        grid[i][j] = 0
                return False
    return True

試しにコードを実行した結果を正解と比較してみましょう。見比べてみると、どちらも結果が同じなので、答えは合っていそうです。ただし前回の問題作成の時もそうでしたが、プログラム自体は何も効率化されておらずfor文の多重ネストをはじめとした実装になっているので、実行時間はほとんどかからないものの、改良の余地はありそうです。

answer_1.txt                        solve_1.txt
==============================      ==============================
Sudoku Solution #1                  Sudoku Puzzle #1 - Solution
Difficulty: low                     Difficulty: low
Generated: 2025-09-30 20:19:48      Generated: 2025-09-30 20:19:48
                                    Solved: 2025-10-01 22:40:33
==============================      ==============================

4 8 9 | 3 1 2 | 5 7 6               4 8 9 | 3 1 2 | 5 7 6
6 5 3 | 4 7 9 | 1 2 8               6 5 3 | 4 7 9 | 1 2 8
7 2 1 | 6 8 5 | 4 3 9               7 2 1 | 6 8 5 | 4 3 9
---------------------               ---------------------
5 4 7 | 1 3 8 | 9 6 2               5 4 7 | 1 3 8 | 9 6 2
3 6 2 | 5 9 7 | 8 4 1               3 6 2 | 5 9 7 | 8 4 1
9 1 8 | 2 6 4 | 7 5 3               9 1 8 | 2 6 4 | 7 5 3
---------------------               ---------------------
2 9 4 | 8 5 3 | 6 1 7               2 9 4 | 8 5 3 | 6 1 7
1 7 5 | 9 2 6 | 3 8 4               1 7 5 | 9 2 6 | 3 8 4
8 3 6 | 7 4 1 | 2 9 5               8 3 6 | 7 4 1 | 2 9 5

まとめ

今回は昨日claude codeに作成させた数独の問題をclaude codeで解かせるためのコードを作成させました。パッとみた限りではちゃんと溶けていますが、計算効率がとても悪い実装になっているので、プロファイリングもしつつ性能を改善していきたいと思います。

Discussion