ChatGPTでプログラミング(四則演算の問題生成)

# はじめに
以前、簡単な四則演算の問題を大量に記録したcsvファイルを作成する必要があったので，プログラムの力で解決しようと思います。なおかつ、コーディンの時間も抑えたいため、ChatGPT の力を借りて開発していきます。[「おわりに」](#おわりに)でチャットの内容を共有していますので、ご確認ください。

システムを開発する際には、「要件定義 → 設計 → 実装 → テスト」という流れが定番です。本記事では、「設計」としてプログラムの仕様を入力して、ChatGPT に「実装」の工程を担ってもらいます。

# ChatGPT のプログラミング
ChatGPT に要求するプログラムを出力させるには、プログラムに関する情報を正確に入力する必要があります。私たち人間は、ChatGPT に対してどのような情報を伝えるべきか、実際に ChatGPT に聞いてみましょう。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2844961/a50371c9-c880-c319-d8ad-5b11652db8bf.png)

環境が特殊な場合は、「3．開発環境」について、詳細な情報を記述する必要があるかと思われます。

希望するプログラムにより近いものを ChatGPT に出力させるには、「5．制約や条件」をいかに正確かつ詳細に記述するかが重要です。本記事では、この項目を「仕様」と題し，細かく定義して ChatGPT を利用しました。

# 要件定義
システム開発の流れに沿って、まずは、プログラムが果たすべき要件を簡潔に考えていきます。例えば、筆者の場合は「csvファイルを作成し、そこに簡単な四則演算の問題を大量に記録する」というのが要件にあたります。

# 設計
次に「設計」をしていきます。ここでは要件定義の内容をより詳細に決めて、プログラムの仕様をまとめていきます。プログラムがどのような役割や機能をもつかを思いつく限り考えましょう。

例えば、筆者の場合は次の内容が仕様にあたります。
```:プログラムの仕様
- プログラミング言語はPythonとする
- csvファイル「question.csv」を作成する
- csvファイルの行は表形式の行にあたり，「,」で列に区切られる
- 「question.csv」の0行目(ファイルの先頭)は「No,問題,回答」である．
- 1行目以降の内容について，次のことを守れ
- 0列目(表における最初の列)は問題番号を示す．すなわち1行目の0列目は「1,」となる．
- 1列目に演算の問題を記述する．
- 四則の記号は加算を「+」，減算を「-」，乗算を「x」，除算を「÷」とする．
- 除算については，解が整数になるものに限る．
- 出題する四則演算で用いる数は1桁の2つの整数でランダムに表されるものとする．
- 要件を満たす例として，加算の場合は「1+1」，減算の場合は「1-1」，
  乗算の場合は「1×1」，除算の場合は「1÷1」となる．
- 要件を満たさない例として，「1*1」は記号が「×」ではないため正しくない，
  「10+1」は「100」が2桁以上の数であるため正しくない，
  「1÷3」は解が整数ではないため正しくないなどが挙げられる
- 2列目には1列目の四則演算の解が記述される．
```
たった1文の要件定義が、13項目の設計仕様に膨れ上がりましたね。各項目の内容を読めば、「それはそうだ」という内容になっていることがわかるかと思われます。

7項目「1列目に演算の問題を記述する．」以降は、「<INS>簡単な</INS> 四則演算」の定義を細かく連ねたものです。「簡単な」という単語は抽象度が高く、私たち人間でも認識がずれます。そのため、筆者は思いつく限り、詳細に説明をしました。

# 実装
それでは、設計で記述した内容を ChatGPT に入力しましょう。メッセージの最初に「次の仕様を満たすプログラムを書け」と指示をしてあげると丁寧かもしれません。

筆者の場合は次のプログラムが出力されました。便宜上、これを「test.py」とします。
```py: 出力されたプログラム test.py
import csv
import random

def generate_question():
    # ランダムに1桁の整数を生成
    num1 = random.randint(1, 9)
    num2 = random.randint(1, 9)

    # ランダムに四則演算を選択
    operator = random.choice(['+', '-', 'x', '÷'])

    # 除算の場合は解が整数になるように調整
    if operator == '÷':
        result = num1 * num2
        num1 = result

    # 問題と解を返す
    question = f"{num1} {operator} {num2}"
    result = str(eval(question))  # evalを使用して四則演算の結果を計算

    return question, result

def create_csv(file_path, num_questions):
    # CSVファイルのヘッダー
    header = ["No", "問題", "回答"]

    with open(file_path, 'w', newline='', encoding='utf-8') as csv_file:
        writer = csv.writer(csv_file)
        
        # ヘッダーの書き込み
        writer.writerow(header)

        # 問題と回答の生成・書き込み
        for i in range(1, num_questions + 1):
            question, result = generate_question()
            writer.writerow([str(i), question, result])

if __name__ == "__main__":
    file_path = "question.csv"
    num_questions = 10  # 生成する問題の数を指定

    create_csv(file_path, num_questions)
    print(f"{file_path} が正常に生成されました。")
```

ここで、プログラムの内容をよく理解できない場合は、「コメントアウトを用いてプログラムをより詳細に解説せよ」と指示して、再度 ChatGPT に回答させると良いでしょう。

# テスト
得られたプログラムをコピーして、ソースファイル test.py を作成し、実際に実行しましょう。
```
~ $ python .\test.py
Traceback (most recent call last):
  File "~\test.py", line 42, in <module>
    create_csv(file_path, num_questions)
  File "~\test.py", line 35, in create_csv
    question, result = generate_question()
                       ^^^^^^^^^^^^^^^^^^^
  File "~\test.py", line 19, in generate_question
    result = str(eval(question))  # evalを使用して四則演算の結果を計算
                 ^^^^^^^^^^^^^^
  File "<string>", line 1
    16 ÷ 4
       ^
SyntaxError: invalid character '÷' (U+00F7)
```

エラーが出力されました。これは、```eval()``` 関数によって引数の文字列を計算式として解釈するときに、除算の記号「 ```÷``` 」が無効な文字であることを示しています。

では、エラー文を ChatGPT に入力して、デバッグを試みてみます。エラー文の前に「次のエラーを解決するように、前述の仕様を守りつつプログラムを書き替えよ。」と指示してあげると丁寧かもしれません。

```py: 修正されたプログラム
import csv
import random

def generate_question():
    # ランダムに1桁の整数を生成
    num1 = random.randint(1, 9)
    num2 = random.randint(1, 9)

    # ランダムに四則演算を選択
    operator = random.choice(['+', '-', 'x', '/'])

    # 除算の場合は解が整数になるように調整
    if operator == '/':
        result = num1 * num2
        num1 = result

    # 問題と解を返す
    question = f"{num1} {operator} {num2}"
    result = str(eval(question))  # evalを使用して四則演算の結果を計算

    return question, result

def create_csv(file_path, num_questions):
    # CSVファイルのヘッダー
    header = ["No", "問題", "回答"]

    with open(file_path, 'w', newline='', encoding='utf-8') as csv_file:
        writer = csv.writer(csv_file)
        
        # ヘッダーの書き込み
        writer.writerow(header)

        # 問題と回答の生成・書き込み
        for i in range(1, num_questions + 1):
            question, result = generate_question()
            writer.writerow([str(i), question, result])

if __name__ == "__main__":
    file_path = "question.csv"
    num_questions = 10  # 生成する問題の数を指定

    create_csv(file_path, num_questions)
    print(f"{file_path} が正常に生成されました。")
```
```generate_question()``` 関数にある ```operator``` 変数に注目してください。除算の記号が「 ```÷``` 」から「 ```/``` 」に変化しました。確かに、この修正によって先ほどのエラーは発生しないことが推測できます。

# 再実装(デバッグ)
修正されたプログラムはエラーを解決しているかもしれませんが、「```四則の記号は ・・・ 除算を「÷」とする．```」という設計の仕様を満たしていません。これが、ChatGPT を用いた開発の限界です。人間側の能力による制約でもあります(当然、筆者の指示が下手であることは否めません)。

解決アプローチとして、もっと正確に指示を入力したり、繰り返し修正を求めたり、もっと上質な AI (例えば ChatGPT4.0) を導入したりなどが挙げられます。どのアプローチをとるかは、ご自身の好みで選択をしてください。

筆者は、ChatGPT で実装しきれなかった箇所は、自分で補うことで解決したいと思います。test.py を次のように修正します。

- ```ope``` 変数の宣言
    - ```eval()``` 関数に適した演算記号を保存する 
    - 初期値は、計算式の符号 ```operator``` とする
- ```ope``` 変数の管理
    - 除算の場合は ```ope``` に ```/``` を代入
    - 乗算の場合は ```ope``` に ```*``` を代入

```py: fixed.py
import csv
import random

def generate_question():
    # ランダムに1桁の整数を生成
    num1 = random.randint(1, 9)
    num2 = random.randint(1, 9)

    # ランダムに四則演算を選択
    operator = random.choice(['+', '-', 'x', '÷'])  # 表示する計算式の符号
    ope = operator                                  # eval()関数用の符号

    # 除算の場合は解が整数になるように調整かつ符号の修正
    if operator == '÷':
        result = num1 * num2
        num1 = result
        ope = '/'               # 符号の修正
    # 乗算の場合も符号の修正が必要
    elif operator == 'x':
        result = num1 * num2
        num1 = result
        ope = '*'               # 符号の修正

    # 問題と解を返す
    question = f"{num1} {operator} {num2}"      # 表示する四則演算の式
    result = str(eval(f"{num1} {ope} {num2}"))  # evalを使用して四則演算の結果を計算

    return question, result

def create_csv(file_path, num_questions):
    # CSVファイルのヘッダー
    header = ["No", "問題", "回答"]

    with open(file_path, 'w', newline='', encoding='utf-8') as csv_file:
        writer = csv.writer(csv_file)
        
        # ヘッダーの書き込み
        writer.writerow(header)

        # 問題と回答の生成・書き込み
        for i in range(1, num_questions + 1):
            question, result = generate_question()
            writer.writerow([str(i), question, result])

if __name__ == "__main__":
    file_path = "question.csv"
    num_questions = 10  # 生成する問題の数を指定

    create_csv(file_path, num_questions)
    print(f"{file_path} が正常に生成されました。")

```

修正後、実行をしてみます。

```
~ $ python .\fixed.py
question.csv が正常に生成されました。
```

これで csv ファイル question.csv が作成されましたので、中身を確認します。実行するたびに内容は書き換えられるので、基本的に下記のものと一致はしませんが、例としてご参考ください。

```csvs: question.csv
No,問題,回答
1,2 ÷ 1,2.0
2,7 + 4,11
3,6 + 6,12
4,72 ÷ 8,9.0
5,6 x 1,6
6,1 x 3,3
7,30 ÷ 5,6.0
8,7 + 3,10
9,9 x 7,63
10,7 - 3,4
```

動作も問題がなさそうです。fixed.py のメイン関数にある ```num_questions``` 変数の値を変更すれば、任意の問題数(数百、数千問)を作成することが可能です。ただし、調子に乗りすぎると csv ファイルの容量が大きくなるので注意してください。

# おわりに
今回は ChatGPT を用いてプログラミングをしていきました。実際の作業時間としては十数分程度で終了したので、時間が限られているときの効率的な開発方法として非常に優秀です。

しかし、AI も完璧ではないので、私たち人間は動作に異常がないか、ソースコードに問題がないかを確認するべきです。プログラムで生じた損害は、AI ではなく人間が負うため、技術の進化に甘え過ぎないように気を付けないといけないですね。

本記事で行ったチャットのやりとりを次の URL で共有します。

https://chat.openai.com/share/184c78d1-201e-4865-8a2c-8b0a789b6a55