検索
連載

苦手な「プログラミング」を助けてくれるChatGPT まずはExcelから(第9回)

OpenAIが公開した初歩的な「ChatGPT」向けのプロンプトを紹介しよう。今回はプログラミングにChatGPTを使う例だ。

PC用表示
Share
Tweet
LINE
Hatena

 OpenAIが公開した「ChatGPT」の使いこなしに役立つ短いプロンプトを紹介しよう(連載記事の一覧はこちら)。今回は日本語の文を扱うのではなく、プログラミングを助けてもらおう。ChatGPTは自然言語だけでなく、さまざまなプログラミングにも有効だということが分かっているからだ。

 まずは単純な例から試してみよう。ある程度慣れてきたら、プロンプトを変えたり、これまでの連載で紹介したテクニックを試したりするとよいだろう。どのプロンプトも無償のChatGPT 3.5で利用できる。

(1)Excel関数を教えてもらう

 まずは簡単な例だ。「Microsoft Excel」(以下、Excel)で処理したい内容があるとき、それを実行する関数をすぐに思いつかない場合があるだろう。

 早速試してみよう。

Excel関数について質問があります。セルA1からセルA20まで整数が入っているセルが並んでいます。このうち、2番目に大きな数が入っているセルを返す関数を教えてください。


Excelの関数を使って、2番目に大きな数を返す方法は幾つかありますが、その中でも便利な関数はLARGE関数です。以下に例を示します。

まず、セルA1からA20までに整数が入っていると仮定します。次に、次のような式を使って2番目に大きな数を取得できます。

=LARGE(A1:A20, 2)

この式では、LARGE関数を使ってセル範囲A1からA20の中で2番目に大きな数を取得しています。,の後の数値2は、2番目に大きな数を取得するためのパラメーターです。

この式を適用すると、2番目に大きな数が含まれるセルの値が返されます。

なお、この方法はセル内の重複する数値を考慮せず、単純に数値の大小だけを見ています。


(2)仕様を与えてPythonのコードを生成する

 次は仕様を与えて「Python」のコードをChatGPTに出力してもらおう。まずは短い関数を試してみよう。

画像へのファイルパスを入力とし、画像をnumpy配列としてメモリにロードし、行と列がしきい値より暗ければ周囲を切り抜くPython関数を書いてください。行と列の平均値を使用して、削除のためにマークされるべきかどうかを決定してください。


 これは難易度が高そうだが、ChatGPTは何と答えてくるだろうか。

from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
  model="gpt-4",
  messages=[
    {
      "role": "user",
      "content": "Write a Python function that takes as input a file path to an image, loads the image into memory as a numpy array, then crops the rows and columns around the perimeter if they are darker than a threshold value. Use the mean value of rows and columns to decide if they should be marked for deletion."
    }
  ],
  temperature=0.7,
  max_tokens=64,
  top_p=1
)

(3)SQLを生成する

 Excelの関数やPythonのコード以外にも、ChatGPTはさまざまなプログラミング言語を処理できる。例えばSQLだ。データベース管理システムを使うには、SQLでクエリを記述して必要な処理を実行する必要がある。これもChatGPTに任せてみよう

次のSQLテーブルを使って、ユーザーの要求に従い、クエリを作成してください。

    CREATE TABLE Orders (
      OrderID int,
      CustomerID int,
      OrderDate datetime,
      OrderTime varchar(8),
      PRIMARY KEY (OrderID)
    );
    
    CREATE TABLE OrderDetails (
      OrderDetailID int,
      OrderID int,
      ProductID int,
      Quantity int,
      PRIMARY KEY (OrderDetailID)
    );
    
    CREATE TABLE Products (
      ProductID int,
      ProductName varchar(50),
      Category varchar(50),
      UnitPrice decimal(10, 2),
      Stock int,
      PRIMARY KEY (ProductID)
    );
    
    CREATE TABLE Customers (
      CustomerID int,
      FirstName varchar(50),
      LastName varchar(50),
      Email varchar(100),
      Phone varchar(20),
      PRIMARY KEY (CustomerID)
    );

2023-04-01の全注文の平均注文総額を計算するSQLクエリを書いてください。


2023-04-01の全ての注文の平均合計注文金額を計算するには、Orders、OrderDetails、Productsテーブルを結合する必要があります。各注文のQuantityとUnitPriceの積を合計し、それらの合計の平均を計算します。

以下はSQLクエリです:

SELECT AVG(TotalOrderValue) AS AverageOrderValue
FROM (
  SELECT Orders.OrderID, SUM(OrderDetails.Quantity * Products.UnitPrice) AS TotalOrderValue
  FROM Orders
  JOIN OrderDetails ON Orders.OrderID = OrderDetails.OrderID
  JOIN Products ON OrderDetails.ProductID = Products.ProductID
  WHERE Orders.OrderDate = '2023-04-01'
  GROUP BY Orders.OrderID
) AS OrderValues;

このクエリは、まずサブクエリで各オーダーの合計値を計算し、次にアウタークエリでこれらの値の平均を計算します。


(4)コードを説明する

 コードを書き下すのではなく、他人の書いたプログラムを読み解かなければならない場面もあるだろう。前任者が残したマクロやスクリプトといった単純なものから、複雑な処理までいろいろある。どの場合も処理の流れを追いながらきちんと理解するには、時間がかかるものだ。このようなときはプログラムをChatGPTに与えて説明してもらうとよい。

プログラムのコードの一部を渡すので、簡潔に説明してください。


class Log:
        def __init__(self, path):
            dirname = os.path.dirname(path)
            os.makedirs(dirname, exist_ok=True)
            f = open(path, "a+")
    
            # Check that the file is newline-terminated
            size = os.path.getsize(path)
            if size > 0:
                f.seek(size - 1)
                end = f.read(1)
                if end != "\n":
                    f.write("\n")
            self.f = f
            self.path = path
    
        def log(self, event):
            event["_event_id"] = str(uuid.uuid4())
            json.dump(event, self.f)
            self.f.write("\n")
    
        def state(self):
            state = {"complete": set(), "last": None}
            for line in open(self.path):
                event = json.loads(line)
                if event["type"] == "submit" and event["success"]:
                    state["complete"].add(event["id"])
                    state["last"] = event
            return state

 これはPythonで書かれたコードのようだが、ChatGPTは果たしてちゃんと説明してくれるだろうか。

このコードでは、イベントをファイルに記録したり、ログの状態を取得したりするために使用する`log`というクラスを定義しています。

`__init__`メソッドはログファイルを初期化します。引数としてファイルパスを取り、ディレクトリが存在しない場合はそのディレクトリを作成し、追記モードでファイルを開きます。

`log`メソッドはファイルにイベントを記録します。log`メソッドは辞書を引数に取り、一意の識別子を追加してJSONに変換し、改行とともにファイルに書き込みます。

`state`メソッドはログの状態を取得します。2つのキーを持つ辞書を初期化します:complete"(完了したイベントのセット)と"last"(最後のイベント)です。そして、ログファイルを1行ずつ読み込み、各行をJSONとして解析し、イベントのタイプが"submit"で成功した場合、そのイベントIDを"complete"セットに追加し、"last"イベントを更新します。最後に、ステート辞書を返します。


(5)プログラムのバグを見つけ出す

 プログラミングで最も時間を取られる作業の一つはバグの除去だろう。これもChatGPTに助けてもらおう。

Pythonのコードを渡すので、バグを見つけて修正してください。


import Random
    a = random.randint(1,12)
    b = random.randint(1,12)
    for i in range(10):
        question = "What is "+a+" x "+b+"? "
        answer = input(question)
        if answer = a*b
            print (Well done!)
        else:
            print("No.")

 ChatGPTはこのコードからバグを幾つ見つけられるだろうか。

提供されたコードにはいくつかのバグがあります:

1. import文が正しくない。Pythonは大文字と小文字を区別するので、"Random"は"random"とすべきです。

2. 変数`a`と`b`がループの外側で定義されています。

3. 異なる質問を生成したい場合は、ループの内側に置く必要があります。問題文字列の`a`と`b`は整数なので、`str()`関数を使用して文字列に変換し、連結する必要があります。

4. if文では、代入演算子である等号`=`を使っています。比較には二重の等号`==`を使うべきです。

5. print文の"Well done!"が引用符で囲まれていません。Pythonでは文字列は引用符で囲む必要があります。

6.ユーザーからの入力は文字列なので、`a`と`b`の積と比較する前に`int()`関数を使って整数に変換する必要があります。

正しいコードは次の通りです。

import random
for i in range(10):
    a = random.randint(1,12)
    b = random.randint(1,12)
    question = "What is "+ str(a) +" x "+ str(b) +"? "
    answer = int(input(question))
    if answer == a*b:
        print ("Well done!")
    else:
        print("No.")

(6)コード効率の改善

 仕様通りに動作するプログラムが完成してバグがなくなったとしても、動作速度に満足がいかない場合もあるだろう。動作効率を改善できないだろうか。こんなときにも、ChatGPTが役に立つ。

Pythonのコードの一部を次に渡します。効率を改善するためのアイデアを提供してください。


def has_sum_k(nums: List[int], k: int) -> bool:
        """
        Returns True if there are two distinct elements in nums such that their sum 
        is equal to k, and otherwise returns False.
        """
        n = len(nums)
        for i in range(n):
            for j in range(i+1, n):
                if nums[i] + nums[j] == k:
                    return True
        return False

現在の実装では、forループが入れ子になっているため、計算量はO(n^2)です。これは、これまで扱った数を格納するセットを使用することでO(n)に改善できます。以下に改善したコードを示します:

def has_sum_k(nums: List[int], k: int) -> bool: ▼○○
 numsの中に2つの異なる要素があり、それらの和がkに等しい場合にTrueを返し、そうでない場合はFalseを返します。
▼○pw
seen = set()
for num in nums:
        if k - num in seen:
            return True
        seen.add(num)
return False

このコードでは、数字のリストを一度だけ繰り返し処理します。各数値について、その補数(k - num)がこれまで見てきた数字の集合の中にあるかどうかをチェックします。もし入っていれば、和がkになる2つの数字が見つかったのでTrueを返します。和がkになる2つの数を見つけられずにリストの反復処理を終えた場合、Falseを返します。

このアプローチにより、時間の複雑さがO(n^2)からO(n)になり、大きなリストでは大幅に改善できます。空間の複雑さもO(n)です。なぜなら、最悪の場合、セット内の全ての数値を保存する必要があります。しかし、これは時間の複雑さの改善とトレードオフの関係にあります。


 もともとのコードだと、nの大きさが10倍になったとき、計算量(ほぼ処理時間と等しい)は100倍に増えてしまう。ChatGPTが修正したコードなら、nが10倍になっても計算量は変わらない。明らかな改善だ。

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る