coLinux日記

coLinuxはフリーソフトを種として、よろずのシステムとぞなれりける。

入門者が、Python プログラミングにおいて、生成AI(Copilot)を使ってコメントとdocstring を付ける。

2024-10-30 18:11:29 | Python
以前紹介した、
PyFlo The beginners guide to becoming a Python programmer
https://pyflo.net/
の中で、「Guided Project 1」をクリックすると、
Flag Creater
https://pyflo.net/flag/
というものがありました。

文化の日も近いので、日の丸を文字で作るプログラムを作ってみました。
Wikipedia で日章旗を調べると、
縦を2とすると横が3、その中央にある円の直径が縦の 3/5 となっています。
縦を 2.0 とすると、横は 3.0 となり、 中央の円の半径は 2 * (3/5) / 2 = 0.6 となります。
半角文字の大きさの比率がだいたい縦:横 2:1 となるので、1行の文字数は旗の左下隅を X-Y平面の原点においたとして、
その座標は、
左上 (0,20) 、 右上 (60,20) 、 
左下 (0,0) 、 右下 (60,0)
としました。変換してそれぞれ、(0.0 , 2.0) 、(3.0 , 2.0)、 (0.0 , 0.0)、(3.0 , 0.0)になります。

これでプログラムを作ってみます。関数も使わない単純なものですが、このプログラムにコメントや docstring を入れるために、 Microsoft Copilot を使いました。

まず、docstring 生成を次のプロンプトで依頼しました。そのとき、プログラムがどのようなものかを簡単に説明する文を陽に入れてみました。

「次のPython プログラムの docstring を生成して下さい。
これは、日章旗を文字で生成するためのプログラムです。
日章旗は、縦の長さを2、幅の長さを3、中央の日の丸の半径を 0.6 になるように作成します。

w = 60
h = 20
a0 = '+' + '-'*(w-1) + '+'
a20 = '+' + '-'*(w-1) + '+'
a = '|' + ' '*(w-1) + '|'

print(a0)
for i in range(1,h):
   y = (h - i) / (h / 2)

   b = a[0]
   for j in range(1,w):
      x = j / (w / 3)

      if ( (x-1.5)**2 + (y-1.0)**2 < 0.36 ) :
         b += '*'
      else:
         b += a[j]
   b += a[-1]

   print(b)

print(a20) 」


Copilot の回答は以下の通りでした。

ここから======
もちろん、こちらがあなたのプログラムのためのdocstringです:

def draw_nisshoki():
   """
   This function prints the Japanese national flag (Nisshoki) using ASCII characters.

   The flag's proportions are a height to width ratio of 2:3, with a red circle (the sun)
   in the center. The circle's radius is 0.6, scaled appropriately to fit within the grid.

   The border of the grid is made up of '+' and '-' characters for the top and bottom,
   and '|' characters for the sides. The sun is drawn using '*' characters inside the grid.

   Variables:
   w (int): The width of the grid.
   h (int): The height of the grid.
   a0 (str): The top border of the grid.
   a20 (str): The bottom border of the grid.
   a (str): The side borders of the grid.

   The function uses nested loops to calculate the positions of the '*' characters
   that form the sun based on the coordinates (x, y) within the grid.
   The equation `( (x-1.5)**2 + (y-1.0)**2 < 0.36 )` is used to determine whether
   a particular point should be part of the sun shape or not.
   """
   w = 60
      .................
      (プロンプトで入力したプログラムなので途中省略)
      .................
   print(a20)

======ここまで


関数にされてしまいました。しかもなぜか英語でした。これは、docstring は、関数やクラスの中で使うかららしいです。 一応 DeepL で翻訳(少し修正あり)しておきます。

この関数は、日本の国旗(日章旗)をASCII文字で印刷します。

 国旗の縦横比は2:3で、中央に赤い円(太陽)がある。円の半径は0.6で、グリッドに収まるように適切に拡大縮小されています。

 グリッドの境界線は、上下に'+'と'-'、左右に'|'で構成されている。太陽はグリッドの内側に'*'文字を使って描かれます。

 変数

 w (int): グリッドの幅。
 h (int): グリッドの高さ。
 a0 (str): グリッドの上辺。
 a20 (str): グリッドの下端。
 a (str): グリッドのサイドボーダー。

 この関数は,ネストされたループを使用して,グリッド内の座標 (x, y) に基づいて太陽を形成する '*' 文字の位置を計算します.

 方程式 `( (x-1.5)**2 + (y-1.0)**2 < 0.36 )` は、特定の点が太陽の形に含まれるかどうかを決定するために使用されます。

プログラムの内容を、ほぼ正確に表してくれていますね。

そこで、今度はこのプロンプトの docstring を コメント にして再び Copilot にたずねました。

「次のPython プログラムを説明するための分かりやすいコメントをプログラムに追加して下さい。
これは、日章旗を文字で生成するためのプログラムです。
日章旗は、縦の長さを2、幅の長さを3、中央の日の丸の半径を 0.6 になるように作成します。
(この後のプログラムは同じなので省略)」


Copilot の回答は以下の通りでした。

ここから======
以下に、プログラムにコメントを追加しました。これで、各部分の動作がわかりやすくなると思います。

```python
# 幅と高さを設定
w = 60
h = 20

# 上部と下部の枠線を作成
a0 = '+' + '-'*(w-1) + '+'
a20 = '+' + '-'*(w-1) + '+'
# 空白の行を作成
a = '|' + ' '*(w-1) + '|'

# 上部の枠線を表示
print(a0)

# 高さ分のループを開始
for i in range(1, h):
   # y座標を計算(中心を基準にした相対位置)
   y = (h - i) / (h / 2)

   # 行の開始文字を設定
   b = a[0]
   # 幅分のループを開始
   for j in range(1, w):
      # x座標を計算(中心を基準にした相対位置)
      x = j / (w / 3)

      # 中心からの距離が半径0.6以内なら '*' を追加、それ以外は空白を追加
      if ( (x-1.5)**2 + (y-1.0)**2 < 0.36 ):
         b += '*'
      else:
         b += a[j]
   # 行の終了文字を追加
   b += a[-1]

   # 行を表示
   print(b)

# 下部の枠線を表示
print(a20)
```

このコメントが、プログラムの理解に役立つことを願っています!他に質問があれば教えてくださいね。

======ここまで


一言も伝えていないのに、プログラムだけから、

上枠(a0)、下枠(a20)と解釈したり、
「中心を基準にした相対位置」と解釈したり、
if 文で陽に 0.36 としか書いていないのにプロンプトから?「半径0.6」と解釈したりと、

しっかりとコメントしてくれています。これは良いですね。

以上、簡単なプログラムを作成して Copilot でコメントやdocstring を付けてもらいましたが、満足できるものでした。

ということは、Python 入門者は生成AIを活用して自分がプログラミングしたプログラムに対してコメントや docstring を付けてもらい、それを参考にしてさらに知識を得ることが、かなり有用であると言えそうです。

最後にプログラムを動作させたときの画像を表示しておきます。作成したプログラムは flag.py というファイルです。


docstring は、Python の有用な機能なのは、SimplePrograms で学ぶうちに理解しました。そうであるなら、初心者は生成AIを積極的に活用して docstring を使うという流れになるかもしれませんね。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Raspberry Pi に Python 3.13.0 をインストールする

2024-10-13 07:54:13 | Python
PEP719 で告知されたとおり、Python 3.13.0 が 2024-10-07 にリリースされました。

当ブログの以前の記事の通りの方法で、早速 Raspberry Pi にインストールしてみます。

まずは、ダウンロードして、チェックします。(例によって空白文字は適当です。)

$ wget http://www.python.org/ftp/python/3.13.0/Python-3.13.0.tar.xz

$ md5sum Python-3.13.0.tar.xz;echo 726e5b829fcf352326874c1ae599abaa
726e5b829fcf352326874c1ae599abaa Python-3.13.0.tar.xz
726e5b829fcf352326874c1ae599abaa
$

$ wget http://www.python.org/ftp/python/3.13.0/Python-3.13.0.tar.xz.asc

$ gpg --verify Python-3.13.0.tar.xz.asc
gpg: assuming signed data in 'Python-3.13.0.tar.xz'
gpg: Signature made Mon 07 Oct 2024 08:30:45 BST
gpg:       using RSA key 7169605F62C751356D054A26A821E680E5FA6305
gpg: Good signature from "Thomas Wouters <thomas@python.org>" [unknown]
gpg:        aka "Thomas Wouters <thomas@xs4all.nl>" [unknown]
gpg:        aka "Thomas Wouters <twouters@google.com>" [unknown]
gpg:        aka "Thomas Wouters <thomas.wouters.prive@gmail.com>" [unknown]
gpg:        aka "Thomas Wouters <thomaswout@gmail.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:       There is no indication that the signature belongs to the owner.
Primary key fingerprint: 7169 605F 62C7 5135 6D05 4A26 A821 E680 E5FA 6305

$


10月7日にサインされていますね。
3.12.6 の時と同じように make してみます。

$ tar xvf Python-3.13.0.tar.xz
$ cd Python-3.13.0
$ ./configure --prefix=/usr/local/python --enable-optimizations --with-readline --enable-shared

$ make    (52分かかりました。)
・・・・・・
Total duration: 6 min 52 sec
Total tests: run=9,361 skipped=201
Total test files: run=44/44
Result: SUCCESS
true
・・・・・・
The necessary bits to build these optional modules were not found:
_dbm
To find the necessary bits, look in configure.ac and config.log.

Checked 112 modules (33 built-in, 77 shared, 1 n/a on linux-aarch64, 0 disabled, 1 missing, 0 failed on import)
make[1]: Leaving directory '〇〇〇〇〇〇/Python-3.13.0'
$

$ make test (今回はテストも行ってみました。)
・・・・・・
21 tests skipped:
   test.test_asyncio.test_windows_events
   test.test_asyncio.test_windows_utils test.test_gdb.test_backtrace
   test.test_gdb.test_cfunction test.test_gdb.test_cfunction_full
   test.test_gdb.test_misc test.test_gdb.test_pretty_print
   test_android test_dbm_ndbm test_devpoll test_free_threading
   test_ioctl test_kqueue test_launcher test_msvcrt test_startfile
   test_winapi test_winconsoleio test_winreg test_winsound test_wmi

4 tests skipped (resource denied):
   test_peg_generator test_tkinter test_ttk test_zipfile64

453 tests OK.

Total duration: 13 min 45 sec
Total tests: run=44,312 skipped=1,693
Total test files: run=474/478 skipped=21 resource_denied=4
Result: SUCCESS
$

# make install

/usr/local/python にインストールされているので、環境変数を設定するのも前回通りです。

$ cat python3.sh
export PATH=/usr/local/python/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/python/lib
$
$ source python3.sh

$ python3 --version
Python 3.13.0
$


これで、Python 3.13.0 のインストールは一応完了です。

https://www.python.org/downloads/release/python-3130/
の中で、(Google Chrome による翻訳)
New features
・ PyPyをベースとした、新しく改良された対話型インタプリタです。複数行の編集や色のサポート、色付きの例外トレースバックが特徴です。


となっていたので、試してみました。

$ python3
Python 3.13.0 (main, Oct 8 2024, 02:36:16) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
(プロンプトやエラーメッセージの一部などがピンク色になった。)
>>> for num in [1,2,3,4]:
...        print(nim)
...
Traceback (most recent call last):
File "<python-input-0>", line 2, in <module>
    print(nim)
    ^^^
NameError: name 'nim' is not defined. Did you mean: 'num'?
>>> for num in [1,2,3,4]:
...        print(nim)
(上矢印キーでいっぺんに表示される。修正も可能!)

>>> for num in [1,2,3,4]:
...        print(num)
...
1
2
3
4
>>>

これは、便利ですね。

さて、New featuresのもう一つ、
・ 予備的、実験的なJITであり、大幅なパフォーマンス向上のための土台となる。

ですが、これは Just In Time コンパイラのことらしいです。これを実現するには、configure のオプション、

--enable-experimental-jit
    build the experimental just-in-time compiler (default is no)


を加える必要があります。試しにこれを加えてmake すると、

$ ./configure --prefix=/usr/local/python --enable-optimizations  --with-readline --enable-shared --enable-experimental-jit
$ make
・・・・
    | raise RuntimeError(f"Can't find {tool}-{_LLVM_VERSION}!")
    | RuntimeError: Can't find clang-18!
   +---------------- ... ----------------
    | and 219 more exceptions
   +------------------------------------
make[2]: *** [Makefile:3029: jit_stencils.h] Error 1
make[2]: Leaving directory '/home/espiya/src/Python-3.13.0-jit'
make[1]: *** [Makefile:889: profile-gen-stamp] Error 2
make[1]: Leaving directory '/home/espiya/src/Python-3.13.0-jit'
make: *** [Makefile:901: profile-run-stamp] Error 2
$

この Raspberry pi ではうまく行きませんでした。残念。
clang はシステムのものでも Ver.16 なので、自分でインストールする必要があります。しかも、clang は LLVM なので、ちょっと大変そうです。
Python って、GCC でなくて、LLVM 推しなのですね。 そのうち試して見たいと思います。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

入門者が生成AIも利用して Python プログラミングを行う。 その4

2024-10-06 16:10:01 | Python
前回までにすべての関数が作成できました。 後は、これらを集めてプログラミングするだけです。

Raspberry Pi が遅いので、最初に考えた検索のループで毎回 fill_list()を使って行リストを作るのは効率が悪そうです。1回だけ実行して、
f_R1,f_R2,f_R3,f_R4,f_R5,f_R6,f_R7,f_R8,f_R9
に代入することにしました。

更に、3行ずつブロックの検査をして、最後に検索するリスト数を減らしてから、列の検査をすることにしました。

プログラムはこんな感じです。

関数定義の部分は省略しました。

print('Question: ')
print_answer(R1,R2,R3,R4,R5,R6,R7,R8,R9)

# 条件を検査して、結果を出力する。
# fill_list()を使うので行の検査は終了している。

f_R1 = fill_list(R1)
f_R2 = fill_list(R2)
f_R3 = fill_list(R3)
f_R4 = fill_list(R4)
f_R5 = fill_list(R5)
f_R6 = fill_list(R6)
f_R7 = fill_list(R7)
f_R8 = fill_list(R8)
f_R9 = fill_list(R9)

right_b1 = []
for row1 in f_R1:
   for row2 in f_R2:
      for row3 in f_R3:
         if chk_block(row1,row2,row3) == False:
            continue
         right_b1.append([row1,row2,row3])
         print(row1)
         print(row2)
         print(row3)
         print('-'*28)
print(' 1-3 blocks')

right_b2 = []
for row4 in f_R4:
   for row5 in f_R5:
      for row6 in f_R6:
         if chk_block(row4,row5,row6) == False:
            continue
         right_b2.append([row4,row5,row6])
         print(row4)
         print(row5)
         print(row6)
         print('-'*28)
print(' 4-6 blocks')

right_b3 = []
for row7 in f_R7:
   for row8 in f_R8:
      for row9 in f_R9:
         if chk_block(row7,row8,row9) == False:
            continue
         right_b3.append([row7,row8,row9])
         print(row7)
         print(row8)
         print(row9)
         print('-'*28)
print(' 7-9 blocks')

print('Answer: ')
for bk1 in right_b1:
   for bk2 in right_b2:
      for bk3 in right_b3:
         if chk_col(bk1[0],bk1[1],bk1[2],bk2[0],bk2[1],bk2[2],bk3[0],bk3[1],bk3[2]) == False:
            continue

         # 答えを出力。複数の答えも想定する。
         print_answer(bk1[0],bk1[1],bk1[2],bk2[0],bk2[1],bk2[2],bk3[0],bk3[1],bk3[2])
         print(' ')

print('=== END ===')

メモリがあふれるかもと思いましたが、それは大丈夫でした。しかし、それでも遅い、最初の答えがでるまで半日、他に答えがないことを調べるため全部の検査にさらに1日くらいかかりました。遅いのでちゃんと動いているか不安になり、3つのブロック検査の途中経過も出力してあります。特に2段目(4-6 blocks) の処理が長かったです。

python3 sudoku.py
Question:
0 9 8 | 0 0 6 | 0 0 4
1 0 4 | 0 0 2 | 0 3 0
3 6 7 | 0 0 1 | 0 0 0
------+-------+-------
0 0 0 | 1 0 0 | 4 9 7
0 0 0 | 0 3 0 | 0 0 0
7 4 2 | 0 0 8 | 0 0 0
------+-------+-------
0 0 0 | 2 0 0 | 9 7 6
0 7 0 | 6 0 0 | 5 0 8
6 0 0 | 5 0 0 | 3 4 0
(途中省略)
[6, 9, 1, 5, 7, 8, 3, 4, 2]
----------------------------
[8, 5, 4, 2, 3, 1, 9, 7, 6]
[3, 7, 2, 6, 9, 4, 5, 1, 8]
[6, 9, 1, 5, 8, 7, 3, 4, 2]
----------------------------
7-9 blocks
Answer:
2 9 8 | 3 5 6 | 7 1 4
1 5 4 | 8 7 2 | 6 3 9
3 6 7 | 4 9 1 | 2 8 5
------+-------+-------
8 3 6 | 1 2 5 | 4 9 7
9 1 5 | 7 3 4 | 8 6 2
7 4 2 | 9 6 8 | 1 5 3
------+-------+-------
5 8 1 | 2 4 3 | 9 7 6
4 7 3 | 6 1 9 | 5 2 8
6 2 9 | 5 8 7 | 3 4 1

=== END ===
$

入門なので、自分で作る関数作成はなるべく単純にしてみました。そんなわけで同じ事を3回も繰り返したりして無駄に長くなっているかもしれませんが、後で分かりやすい事を優先しました。

生成AIも要所で使うことにして、2つの関数生成に使う事ができました。どれを生成AIに任せてどれを自分で作るかは色々と試す必要がありそうです。生成AIの回答の関数は入門者には勉強になる部分も多かったので、ちょっと関数を作るときに気になる事があるなら使って良いのかもしれませんね。途中怪しげなプログラムを提示されました、そこは、回答の通りテストする必要があるわけです。Google Gemini の場合は説明が良いですね。docstring 作成に使った方が良いような気がするのは以前書いた通りでした。

プロンプトに一言も入れていないのに「数独でしょ」と言ってどこかから持ってきた関数を提示するのも面白かったです。

入門者には、生成AIを関数生成とかclass定義とかで活用して、得られたものをテストしたり、その中で知らない機能を使っていたら調べたりする事が経験値を上げるには役立ちそうです。今後生成AIが「この関数はこっちの方が良いですよ。」と言ってくる事は当然ありそうなので、活用したいところです。

Raspberry Pi なので、遅いからスピードアップのために改良するという試行錯誤ができるので、Python入門時には何かと役立ちそうです。
コメント (1)
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

入門者が生成AIも利用して Python プログラミングを行う。 その3

2024-10-05 14:31:04 | Python
前回の Gemini へのプロンプトで最初の「Python で」が抜けていました。

さて、「数独」の3つめ条件、縦9列が1~9までのすべての数字を重複無く含んでいる、を調べる関数
chk_col(c1, c2, c3, c4, c5, c6, c7, c8, c9 ) を定義してみます。
単純に9つの行リストを引数に与えます。
(こちらも gemini は、docstring生成のみに使いました。その中で、Args: Returns: のみ使います。)

def chk_col(c1, c2, c3, c4, c5, c6, c7, c8, c9):
   """
   9つのリストからなる9x9の行列の各列に対して、`check_list()` 関数を適用します。
   全ての列で `check_list()` が `True` を返す場合にのみ、`True` を返します。
   Args:
      c1, c2, ..., c9 (list): 要素数9のリスト。それぞれが9x9行列の1つの列に対応します。
   Returns:
   bool: 全ての列が `check_list()` の条件を満たす場合にTrue、そうでない場合にFalse
   """

   for i in range(0,9):
      column=[c1[i],c2[i],c3[i],c4[i],c5[i],c6[i],c7[i],c8[i],c9[i]]
      if check_list(column) == False:
         return False
   return True

さて、各行の空白の要素を埋めるための関数fill_list()を Google Gemini に作成してもらおうとたところ中々うまく行きませんでした。 記録しなかったので詳しくは書けませんがrandom を使ったり、再帰関数にしたりして答えが正しくない関数を提案されました。

問題は、要素の値が0のインデックスを探して、まだ使っていない数値を求めて、値を埋めていくので、順列ということで、SimplePrograms で出てきた itertools ライブラリを使ってみるのも良さそうだと考えて、次のようなプロンプトにしました。

「Python で、要素数9のリストを A とします。A の各要素の値は1から9までの整数でかつ値が重複できないという条件があります。
今、A のいつかの要素に値が設定されており、まだ値が設定されていない要素の値は 0 になっているとします。この A を引数にして、A の値が0 の所に数値をいれて条件を満たすすべてのリストを求め、それぞれのリストを要素とするリストを返却するような関数fill_list(A) を作成してください。必要なら itertools ライブラリを使ってください。」

Gemini の回答は、以下の様にねらいどおりでした。(それが良いかどうかは別の話)

ここから======
## Pythonでリストを完成させる関数fill_list()の実装

### 問題の理解とアプローチ

* **問題:** 要素数9のリストAがあり、一部の要素に値が設定されている。残りの要素に1から9までの重複のない数値を全てのパターンで割り当て、全ての組み合わせのリストを作成する。
* **アプローチ:**
1. **未設定要素のインデックス:** まず、Aの中で値が0の要素のインデックスをリストで取得する。
2. **順列の生成:** itertools.permutations()を用いて、未設定要素に割り当てる数値の全ての順列を生成する。
3. **リストの生成:** 各順列に対して、元のリストAのコピーを作成し、未設定要素のインデックスに順列の値を順に代入していく。
4. **全ての組み合わせのリスト:** 生成された全てのリストを要素とするリストを返す。

### コードの実装

```python
import itertools

def fill_list(A):
   """
   Aの値が0の所に数値をいれて条件を満たすすべてのリストを返す関数
   Args:
      A: 要素数9のリスト。一部の要素に1から9までの値が設定されている。
   Returns:
      list: 条件を満たす全てのリストのリスト
   """
   # 未設定要素のインデックスを取得
   unset_indices = [i for i, x in enumerate(A) if x == 0]
   # 未設定要素に割り当てる数値の候補
   numbers = [i for i in range(1, 10) if i not in A]
   # 全ての順列を生成
   permutations_list = list(itertools.permutations(numbers))
   # 全ての組み合わせのリストを作成
   result = []
   for perm in permutations_list:
      new_list = A.copy()
      for i, num in zip(unset_indices, perm):
         new_list[i] = num
   result.append(new_list)

   return result
```

### コードの説明

* `unset_indices`: Aの中で値が0の要素のインデックスをリストで取得します。
* `numbers`: Aにまだ含まれていない1から9までの数値をリストで取得します。
* `permutations_list`: itertools.permutations()を用いて、numbersの全ての順列を生成し、リストに変換します。
* `result`: 最終的に返却する、全ての組み合わせのリストを格納する空のリストです。
* `new_list`: Aのコピーを作成し、ここに順列の値を代入していきます。
* `zip(unset_indices, perm)`: unset_indicesとpermの要素を同時に取り出すためにzip関数を使用しています。

### 使用例

```python
A = [1, 0, 3, 0, 5, 6, 0, 8, 9]
result = fill_list(A)
print(result)
```

このコードを実行すると、Aの0の要素に可能な全ての組み合わせで数値を代入したリストのリストが得られます。

### 注意点

* リストの長さが9であるという前提で実装されています。
* 0以外の数値が重複して含まれている場合は、意図した結果にならない可能性があります。
* リストの要素数が大きくなると、生成される組み合わせの数が膨大になり、計算時間が長くなる可能性があります。

このコードは、与えられた条件のもとで、Aの全ての組み合わせを効率的に生成することができます。

======ここまで


念のため、prog04.py というファイルを作って、テストします。最初に fill_list()を回答の通り定義して、テスト部分は、

A = [6, 2, 0, 5, 0, 7, 3, 4, 0]
print(A)
print('-'*28)

result = fill_list(A)

for l in result:
print(l)

です。

$ python3 prog04.py
[6, 2, 0, 5, 0, 7, 3, 4, 0]
----------------------------
[6, 2, 1, 5, 8, 7, 3, 4, 9]
[6, 2, 1, 5, 9, 7, 3, 4, 8]
[6, 2, 8, 5, 1, 7, 3, 4, 9]
[6, 2, 8, 5, 9, 7, 3, 4, 1]
[6, 2, 9, 5, 1, 7, 3, 4, 8]
[6, 2, 9, 5, 8, 7, 3, 4, 1]
$

正しい答えが得られるようになりました。
この回答の中では、SimplePrograms で学んだ enumerate()、リスト内包表記、docstring が使われています。
出てこなかった、リストデータ型のメソッド append() と組み込み関数 zip() を調べて見ました。
https://docs.python.org/ja/3/tutorial/datastructures.html

list.append(x)

   リストの末尾に要素を一つ追加します。a[len(a):] = [x] と等価です。


https://docs.python.org/ja/3/library/functions.html#zip

zip(*iterables, strict=False)

   複数のイテラブルを並行に反復処理し、各イテラブルの要素からなるタプルを生成します。

   以下はプログラム例です:
   >>>

for item in zip([1, 2, 3], ['sugar', 'spice', 'everything nice']):
   print(item)

(1, 'sugar')
(2, 'spice')
(3, 'everything nice')

より正式な定義: zip() は、 i 番目のタプルが 引数に与えた各イテラブルの i 番目の要素を含むような、タプルのイテレータを返します。


itertoolsライブラリでは、permutations()を使っています。
https://docs.python.org/ja/3/library/itertools.html#itertools.permutations

itertools.permutations(iterable, r=None)

   Return successive r length permutations of elements from the iterable.

   r が指定されない場合や None の場合、r はデフォルトで iterable の長さとなり、可能な最長の順列の全てが生成されます。


この問題にぴったりの組合せイテレータですね。

最後にを出力するときにちょっと数独ぽくしたいので、すべての条件を満たした答えを出力する関数print_answer() を作成します。これは問題の出力にも使えますね。(docstring は Gemini によります。)

def print_answer(a,b,c,d,e,f,g,h,i):
   """Prints a 9x9 Sudoku puzzle in a formatted manner.
   Args:
      a, b, c, d, e, f, g, h, i: Lists representing the rows of the Sudoku puzzle.
   """

   print('{0} {1} {2} | {3} {4} {5} | {6} {7} {8}'.format(a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8]))
   print('{0} {1} {2} | {3} {4} {5} | {6} {7} {8}'.format(b[0],b[1],b[2],b[3],b[4],b[5],b[6],b[7],b[8]))
   print('{0} {1} {2} | {3} {4} {5} | {6} {7} {8}'.format(c[0],c[1],c[2],c[3],c[4],c[5],c[6],c[7],c[8]))
   print('------+-------+-------')
   print('{0} {1} {2} | {3} {4} {5} | {6} {7} {8}'.format(d[0],d[1],d[2],d[3],d[4],d[5],d[6],d[7],d[8]))
   print('{0} {1} {2} | {3} {4} {5} | {6} {7} {8}'.format(e[0],e[1],e[2],e[3],e[4],e[5],e[6],e[7],e[8]))
   print('{0} {1} {2} | {3} {4} {5} | {6} {7} {8}'.format(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8]))
   print('------+-------+-------')
   print('{0} {1} {2} | {3} {4} {5} | {6} {7} {8}'.format(g[0],g[1],g[2],g[3],g[4],g[5],g[6],g[7],g[8]))
   print('{0} {1} {2} | {3} {4} {5} | {6} {7} {8}'.format(h[0],h[1],h[2],h[3],h[4],h[5],h[6],h[7],h[8]))
   print('{0} {1} {2} | {3} {4} {5} | {6} {7} {8}'.format(i[0],i[1],i[2],i[3],i[4],i[5],i[6],i[7],i[8]))

何故、Gemini の回答である docstring が英文になったのでしょうか?
これは、Gemini が次のようにこの関数の修正案を提示したからです。入門者にとって衝撃の答えでした。

ここから======(空行は省略)
I'd be glad to provide a response that combines the strengths of both Response A and Response B, addressing their potential shortcomings and incorporating insights from the ratings:

**Improved Response:**
```python
def print_answer(a, b, c, d, e, f, g, h, i):
   """Prints a 9x9 Sudoku puzzle in a formatted manner.
   Args:
      a, b, c, d, e, f, g, h, i: Lists representing the rows of the Sudoku puzzle.
   """
   # Define a separator string for better readability
   separator = "------+-------+-------"
   # Print the puzzle rows with appropriate formatting
   for row in [a, b, c, d, e, f, g, h, i]:
      print(" ".join(row))
      print(separator)
   # Print the final row without the separator
   print(" ".join(i))
```
**Explanation:**
- **Clear Function Definition:** The function is defined with a clear name (`print_answer`) and a docstring that explains its purpose and parameters.
- **Concise and Efficient:** The code is concise and efficient, using a loop to iterate over the rows and a list comprehension to join row elements with spaces.
- **Readability:** The use of a separator string improves readability by visually separating the rows and columns.
- **Flexibility:** The code can be easily adapted to print different puzzle sizes by modifying the separator string and the loop iteration.
- **Correctness:** The code accurately prints the Sudoku puzzle in the desired format.
**Key Improvements:**
- **Combined Strengths:** The response incorporates the best aspects of Response A (conciseness, efficiency) and Response B (clear function definition, docstring).
- **Enhanced Readability:** The separator string enhances visual clarity.
- **Flexibility:** The code is adaptable to different puzzle sizes.

I believe this improved response effectively addresses the prompt and provides a valuable solution.
======ここまで


Gemini は、この関数を数独パズルのためと評価して、その出力ならこの関数をと言うわけですね。
Gemini でこの回答を日本語に訳したものも記録しておきます。(空行は省略)

ここから======
**日本語訳:**
```
def print_answer(a, b, c, d, e, f, g, h, i):
   """9x9の数のパズル (数独) をきれいに表示する関数です。
   引数:
      a, b, c, d, e, f, g, h, i: 数独のパズルの各行を表すリストです。
   """
   # 行と行の間の区切り線として使う文字列
   separator = "------+-------+-------"
# パズルの各行をきれいに表示
   for row in [a, b, c, d, e, f, g, h, i]:
      print(" ".join(row)) # リストの要素をスペースで繋いで表示
      print(separator) # 区切り線を表示
   # 最後の行は区切り線を表示せずに表示
   print(" ".join(i))
```
**コードの説明:**
* **関数 `print_answer`:** 数独のパズルをきれいに表示するための関数です。
* **引数:**
   * `a`, `b`, `c`, ... , `i`: 数独のパズルの各行を表すリストです。それぞれのリストの要素は、その行の数字に対応します。
* **`separator`:** 行と行の間の区切り線として使う文字列です。
* **`for` ループ:** パズルの各行 (`a`, `b`, ... , `i`) を順番に処理します。
* **`print(" ".join(row))`:** リスト `row` の要素をスペースで繋いで一つの文字列にし、それを表示します。これにより、一行の数字が綺麗に表示されます。
* **`print(separator)`:** 区切り線を表示します。
* **最後の `print(" ".join(i))`:** 最後の行は区切り線を表示せずに表示します。
**このコードは、与えられた9つのリスト (各リストが数独の一行を表す) を使って、数独のパズルをコンソール上に綺麗に表示します。**
### より詳しい説明
* **数独:** 9×9のマス目に1から9までの数字を重複なく入れるパズルです。各行、各列、および3×3のブロック内に同じ数字が重複してはいけません。
* **リスト:** Pythonで複数の要素を順番に格納するデータ構造です。このコードでは、各リストが数独の一行に対応しています。
* **`join` メソッド:** リストの要素を指定された文字列で繋いで一つの文字列にするメソッドです。このコードでは、スペースで繋いでいます。
**このコードのポイント:**
* **可読性:** 変数名やコメントによって、コードの意味が分かりやすくなっています。
* **効率性:** `for` ループを使って簡潔に記述されています。
* **汎用性:** 数独のサイズを変更したり、区切り線の文字を変更したりするなど、少し変更するだけで他のパズルにも適用できます。

**もし、数独のパズルを解くプログラムを作りたい場合は、このコードをベースに、数字の配置のルールや解法アルゴリズムを実装する必要があります。**
======ここまで


生成AIを利用したプログラミングは、なかなか興味深いですね。
Gemini の回答に驚いて非常に長くなったので、続きは次回に投稿します。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

入門者が生成AIも利用して Python プログラミングを行う。 その2

2024-10-04 20:49:43 | Python
前回の続きで、具体的なプログラミングを行ってみます。

まず、Google Gemini を使って9要素のリストが1~9のすべての数字を含んでいるかどうかを調べる関数check_list()を作ります。

Gemini へのプロンプトは、以下の通りです。

「要素数9のリスト b を引数として、
リストの各要素が重複しないで1から9の値になっているかどうかを調べて、この条件を満たすときは真、満たさない場合は偽を返すような関数 check_list(b)を作成してください。」

Gemini の回答: (抜粋)
## リストの要素が重複なく1から9まで含まれているか判定する関数

def check_list(b):
   """
   リストbの要素が重複なく1から9まで含まれているかを判定する関数
   Args:
      b: 判定対象のリスト
   Returns:
      bool: 条件を満たす場合はTrue、満たさない場合はFalse
   """

   # 要素数が9でない場合、Falseを返す
   if len(b) != 9:
      return False

   # 要素が1から9の範囲に含まれていないか、重複があるかを確認
   return set(b) == set(range(1, 10))

### コード解説

1. **関数定義:**
   * `check_list(b)`: リスト `b` を引数にとる関数。
2. **要素数チェック:**
   * `if len(b) != 9:`: リストの長さが9でない場合、条件を満たさないためFalseを返す。
3. **重複と範囲チェック:**
   * `return set(b) == set(range(1, 10))`:
      * `set(b)`: リスト `b` を集合に変換することで、重複を自動的に除去する。
      * `set(range(1, 10))`: 1から9までの数値の集合を作成する。
      * `==`: 両方の集合が等しいか(要素が完全に一致するか)を比較する。
      * 集合が等しい場合、リスト `b` の要素が重複なく1から9まで含まれていることを意味するため、Trueを返す。

### 動作原理

* **集合の利用:** 集合は重複した要素を持たないため、リストを集合に変換することで、要素の重複を簡単に検出できる。
* **範囲の比較:** `range(1, 10)` で生成した集合と、リスト `b` から作成した集合を比較することで、要素が1から9までの範囲に含まれているか、かつ重複がないかを同時に確認できる。

=====ここまで


docstring 付きですね。数字が順番に並んでいないので、set()を使ってリストを集合に替えて判定するのですね。
解説も分かりやすく、そのまま利用できます。

次に、「数独」の9個のブロックが1から9の数値を重複無く含んでいるかを調べるために、
3行毎に3回に分けて調べるようにします。一つでも False になると直ぐに終了して次に行くのと、長くなりそうなのでこうしてみました。

そのために、3つの行を引数にして条件を調べる chk_block(a,b,c) という関数を作ってみます。
これは、生成AIを使わないで、単純に次のようにしました。
(Gemini は、docstring作成にのみ使いました。Raises:とExamples:は省略)

def chk_block(a,b,c):
   """
   3つのリストを9要素ずつに分割し、各ブロックに対して `check_list` 関数を適用します。
   全てのブロックで `check_list` が `True` を返す場合にのみ、`True` を返します。
   Args:
      a (list): 要素数9のリスト
      b (list): 要素数9のリスト
      c (list): 要素数9のリスト
   Returns:
      bool: 全てのブロックが `check_list` の条件を満たす場合にTrue、そうでない場合にFalse
   """


   block1 = a[0:3] + b[0:3] + c[0:3]
   block2 = a[3:6] + b[3:6] + c[3:6]
   block3 = a[6:] + b[6:] + c[6:]

   if check_list(block1) == False:
      return False
   if check_list(block2) == False:
      return False
   if check_list(block3) == False:
      return False
   return True

この2つの関数をテストします。
ファイル prog-01.py で、2つの関数を定義して、その下に以下のテストプログラムを挿入して実行します。

row1 = [1, 2, 3, 4, 5, 6, 7, 8, 9 ]
row2 = [4, 5, 6, 7, 8, 9, 1, 2, 3 ]
row3 = [7, 8, 9, 1, 2, 3, 4, 5, 6 ]

if chk_block(row1, row2, row3):
print("OK")
else:
print("NG")

row1 = [1, 2, 3, 4, 5, 6, 7, 8, 9 ]
row2 = [4, 5, 6, 7, 8, 9, 1, 7, 3 ]
row3 = [7, 8, 9, 1, 2, 3, 4, 5, 6 ]

if chk_block(row1, row2, row3):
print("OK")
else:
print("NG")

row1 = [1, 2, 3, 4, 5, 6, 7, 8, 9 ]
row2 = [4, 5, 6, 7, 2, 9, 1, 2, 3 ]
row3 = [7, 8, 9, 1, 2, 3, 4, 5, 6 ]

if chk_block(row1, row2, row3):
print("OK")
else:
print("NG")

row1 = [1, 2, 3, 4, 5, 6, 7, 8, 9 ]
row2 = [4, 5, 6, 7, 8, 9, 1, 2, 3 ]
row3 = [7, 8, 1, 1, 2, 3, 4, 5, 6 ]

if chk_block(row1, row2, row3):
print("OK")
else:
print("NG")

$ python3 prog01.py
OK
NG
NG
NG
$

テストで問題ないことが分かりました。
長くなりましたので、続きはまた後で投稿します。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

入門者が生成AIも利用して Python プログラミングを行う。 その1

2024-10-03 21:32:06 | Python
SimplePrograms を利用して、Pythonの入門も大体終了したので、実際にプログラミングしてみます。

プログラムしようと思ったのは「数独」です。

スマートフォンを使っていると、この問題は解けるか?という宣伝広告が出てくることがあります。いくつか候補を試して進めるしかないような場合があって鬱陶しいので、それをプログラムで解いてしまおうとする試みです。本当に解が1つしかないのかなども試してみたいです。

今回は、問題を与えて解を求めるように作ってみます。最近、生成AIによるプログラミングも話題になっているので使ってみたいと思います。と言っても、全部作ってもらうと入門にならないので、手順はあらかじめ考えておいて、それを実現するような関数を生成AIに作ってもらい実現してみようと思いました。

考えた手順はこんな感じです。

初期化)
1)マスを横方向にみて、9個の要素を含むリストとみなし、上からR1、R2、...、R9 とします。
 各リストには1~9の数字が重複しないで入っている必要があります。
2)空白は0、初期の数字はそのまま(1から9の整数)R1~R9の各要素に代入して初期値にします。

数値挿入)
3)このリストrの0の要素に、残りの数値を入れたリスト(複数できる)をすべて生成するような
関数 fill_list(r)
を作ります。その返却値は、一つの要素が、要素数9個のリストである、リストです。

条件判定)
4)引数で与えたリストの要素が1)の条件を満たすリストかどうかを判定する
関数 check_list(list)
を作ります。
各行は条件をすでに条件をみたしているので、ブロックと列の判定にこれを使います。

答えを探す)
5)話を単純にするために、9重のforループを作り、条件を満たすかどうかを check_list() で判定します。こんな感じでしょうか。

for row1 in fill_list(R1):
   for row2 in fill_list(R2):
      for row3 in fill_list(R3):
         for row4 in fill_list(R4):
            for row5 in fill_list(R5):
               for row6 in fill_list(R6):
                  for row7 in fill_list(R7):
                     for row8 in fill_list(R8):
                        for row9 in fill_list(R9):

 チェックする量が膨大ですが、そのほとんどが条件を満たさないので、満たさなかったらcontinue文を使って直ぐ次に行くようにしたほうが良さそうです。答えが2つ以上ある場合もありそうですから。

列の判定)
6)列の判定は、row1 ~ row9 の各要素を先頭から取り出してマスの縦方向のカラムを表す
9個のリストに対してcheck_list()で判定して、
どれかが偽なら次のループへ、
すべてが真なら列は条件を満たしていることになります。

ブロックの判定)
7)ブロックの判定は、数独の9つのブロックをリストにして、そのリストに対してcheck_list()を実施して、
どれかが偽なら次のループへ、
すべてが真ならブロックの条件を満たしてることになります。
ループの一番内側で使うより、3つの行毎に判定するのが良さそうです。最初の3ブロックで条件を満たさなければ残りの6行を用意して判定する必要はありませんから。

6)、7)の順番は自由です。

こんな感じです。

生成AIは、各関数を作成するのに利用してみます。
上の手順の中で、check_list() と fill_list()の関数を作成してもらいたいと思います。

長くなるので、今後分割して投稿したいと思います。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする