coLinux日記

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

入門者が生成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も利用して Py... | トップ | 入門者が生成AIも利用して Py... »
最新の画像もっと見る

コメントを投稿

Python」カテゴリの最新記事