東京大学のPythonプログラミング入門「3-2. 繰り返し」備忘録

Python勉強中で備忘録です。今回は解説の理解が難しいものがあり、さらに問題が多数あり、かなり時間がかかりました。

⇒これ単独ではwordが定義されていないのでエラーが出る。以下を先に入れておく必要がある。


⇒[7]と[9]の式の違いが良く分からない。出力のデータで、valueの前のカンマの有無の差はあるが。ChatGPTの解答は以下の通り。

① for key in dic1:

dic1 = {'cat': 3, 'dog': 3, 'elephant': 8}
for key in dic1:
    print('key:', key, ', value:', dic1[key])
  • ループ変数 key には キーだけ が順に入ります。
  • 値 (value) を使いたいときは、dic1[key] として改めて辞書から取り出す必要があります。

👉 この場合のイテレーション対象は dic1.keys() と同じです。


② for key, value in dic1.items():

dic1 = {'cat': 3, 'dog': 3, 'elephant': 8}
for key, value in dic1.items():
    print('key:', key, 'value:', value)
  • dic1.items()(キー, 値) のタプル のリスト風オブジェクトを返します。
    例: [('cat', 3), ('dog', 3), ('elephant', 8)]
  • ループでタプルを展開して、keyvalue にそれぞれ代入できます。
  • 値を取り出すのに dic1[key] と書く必要がなく、コードがすっきりします。

✅ 違いまとめ

  • for key in dic1: → キーだけ取り出す。値は dic1[key] で参照。
  • for key, value in dic1.items(): → キーと値を同時に取り出せる。

実用上は、キーと値の両方を使うときは .items() を使う方が読みやすい です。
一方で、キーだけを使いたいときは for key in dic1: の方がシンプルです。

⇒以下の解答を作成したが間違い。ChatGPTの添削は以下の通り。完全に納得。

def reverse_lookup2(dic1):
for key, value in dic1.items():
dic2 = {}
dic2[value] = key
return dic2

なるほど!問題文の条件とコードを照らし合わせると、「辞書をループするたびに dic2 を初期化している」ことが一番の問題です。

あなたのコードを追ってみると:

def reverse_lookup2(dic1):
  for key, value in dic1.items():
    dic2 = {}                # ← ループごとに毎回リセットされる
    dic2[value] = key        # ← 1つだけ登録
    return dic2              # ← すぐに返して終了

問題点

  1. dic2 をループ内で初期化している
    → 毎回 {} からやり直しなので、最後まで回しても1個しか残らない。
  2. return がループの中にある
    → 最初の1回目で関数が終了してしまい、全要素を変換できない。

修正案

  • dic2 = {} はループの前で1度だけ作る。
  • 全部の要素を登録してから return する。
def reverse_lookup2(dic1):
    dic2 = {}
    for key, value in dic1.items():
        dic2[value] = key
    return dic2

⇒ここのプログラミングの式が分からない。具体的には以下が分からない。
・C.append([1]+[0]*i+[1])によって、Cのリストに何が追加される?
・C[i+1][j+1] = C[i][j] + C[i][j+1] によって何が行われる?リストに何が追加される?
・C[:10]の「:10」はiが10までの分表示するということ?

ChatGPTの解説は以下の通り。

このコードは パスカルの三角形 をリストのリスト(2次元配列)として作成する処理です。
一行ずつ役割を見ていきましょう。


コード全体

C = [[1]]
for i in range(100):
    C.append([1] + [0]*i + [1])
    for j in range(i):
        C[i+1][j+1] = C[i][j] + C[i][j+1]

C[:10]

1. 初期値

C = [[1]]
  • まず C はリストのリスト。
  • 1行目(0段目)として [1] を入れておく。
    例:[[1]]

2. 外側のループ

for i in range(100):
  • i今から作る行の1つ前の行のインデックス(0〜99)。
  • ループごとに 新しい行を作ってCに追加していく。

3. 新しい行を追加

C.append([1] + [0]*i + [1])
  • 新しい行(i+1行目)を一旦 端だけ1で中身0 で作る。
  • 例えば i=2 の時は [1] + [0,0] + [1][1, 0, 0, 1]
  • ここで [0]*i は0をi個並べるリスト。

4. 内側のループで値を埋める

for j in range(i):
    C[i+1][j+1] = C[i][j] + C[i][j+1]
  • 新しい行の中の値を前の行から計算する。
  • パスカルの三角形の性質 新しい行の(j+1)番目 = 1つ上の左 + 1つ上の右
  • 例えば上から3行目(i=2 → i+1=3行目)のj=0の計算: C[3][1] = C[2][0] + C[2][1]

5. 結果の取得

C[:10]
  • 最初の10行を表示。
  • 出力例(先頭だけ整形して示す):
[[1],
 [1, 1],
 [1, 2, 1],
 [1, 3, 3, 1],
 [1, 4, 6, 4, 1],
 ...
]

これがパスカルの三角形の最初の10行です。


🔑 まとめ

  • C二項係数を並べたパスカルの三角形。
  • append で新しい行を足し、
  • 前の行の左右の和を使って内側の数値を埋めている。
  • C[:10] で最初の10段分を確認しているだけです

⇒これを見ても完全には理解できず。以下に調べた内容をまとめます。
・改行されているのはpythonの表示が見易さ的に勝手にやっているだけで、実質はリストが並んでいるだけ。
・新しい行を追加というのは新しいリストを追加という方が正しい。
・C[:10]は10行というよりは10要素(10リスト)と言う方が正しい。

⇒再度ChatGPTに解説を要求。一部抜粋します。


i = 0 のとき

  • ループ変数: i = 0
  • ループ開始時の C: [[1]] (行数 = 1、C[0] が存在)
  1. C.append([1] + [0]*0 + [1]) を実行
    • [0]*0[] だから、追加するリストは [1, 1]
    • C[[1], [1, 1]] になる(新しく C[1] ができた)
  2. for j in range(i):range(0) は空なので 内側ループは実行されない(何も更新されない)
  • i=0 終了後の C: [[1], [1, 1]]

i = 1 のとき

  • ループ変数: i = 1
  • 開始時の C: [[1], [1,1]]C[0],C[1] がある)
  1. C.append([1] + [0]*1 + [1])
    • [0]*1 = [0] → 追加するリストは [1, 0, 1]
    • C[[1], [1,1], [1,0,1]](新しく C[2] ができる)
  2. for j in range(i):range(1)j = 0 の1回だけ走る
    • 計算: C[i+1][j+1] = C[i][j] + C[i][j+1]
    • ここでは i+1 = 2, j+1 = 1C[2][1] = C[1][0] + C[1][1] = 1 + 1 = 2
    • C[2][1, 2, 1] に更新される
  • i=1 終了後の C: [[1], [1, 1], [1, 2, 1]]

i = 2 のとき

  • 開始時の C: [[1], [1,1], [1,2,1]]
  1. C.append([1] + [0]*2 + [1])[1, 0, 0, 1] を追加 → C[3] = [1,0,0,1]
  2. for j in range(2):j = 0, 1
    • j = 0:
      • C[3][1] = C[2][0] + C[2][1] = 1 + 2 = 3
      • C[3][1, 3, 0, 1]
    • j = 1:
      • C[3][2] = C[2][1] + C[2][2] = 2 + 1 = 3
      • C[3][1, 3, 3, 1]
  • i=2 終了後の C: [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]

i = 3 のとき

  • 開始時の C: 上の状態(C[3] = [1,3,3,1]
  1. C.append([1] + [0]*3 + [1])[1,0,0,0,1] を追加 → C[4] = [1,0,0,0,1]
  2. for j in range(3):j = 0,1,2
    • j=0: C[4][1] = C[3][0] + C[3][1] = 1 + 3 = 4
    • j=1: C[4][2] = C[3][1] + C[3][2] = 3 + 3 = 6
    • j=2: C[4][3] = C[3][2] + C[3][3] = 3 + 1 = 4
  • i=3 終了後の C: [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]


まとめ(一般形)

  • 各イテレーション i で行われていること:
    1. 新しい行(長さ i+2)を [1, 0, 0, ..., 0, 1] で作って C に追加(これが C[i+1] になる)。
    2. 内側ループ for j in range(i): により、j = 0 .. i-1 の範囲で C[i+1][j+1] = C[i][j] + C[i][j+1] を計算して中身を埋める。
    3. 両端(インデックス 0i+1)は最初から 1 なので変えない。
  • 数学的には C[n][k] = C[n-1][k-1] + C[n-1][k](0 ≤ k ≤ n)を順に適用しているだけです。

⇒やっと大体理解できました。
・append([a]+[b]+[C])でリストに[a,b,c]を追加する。
・リストの掛け算はリストの個数を示す。 例)[0]*2=[0]+[0]
・C[i+1][j+1] = C[i][j] + C[i][j+1]はC[i+1][j+1]に該当するリストの具体的な数を、
 C[i][j] + C[i][j+1]に書き換え

⇒以下の解答を作成したが間違い。典型的なミス。range(2)だとi=0、1まで。range(3)に修正したら正解。

def sum_matrix(list1, list2):
list3 = [[0,0,0],[0,0,0],[0,0,0]]
for i in range(2):
for j in range(2):
list3[i][j] = list1[i][j] + list2[i][j]
return list3

⇒以下の解答を作成したが間違い。「if str2 in str1:」の時点で、内部的にfindのようなメソッドを使っている。繰り返しが止まる位置は「最後に str2 が含まれなくなった位置」で、最初の一致位置ではない(同じ文字列が2回出てくる場合に間違いとなる)。

if str2 in str1:
x =0
while x > 0:
if str2 in str1[x:]:
x +=1
else:
break
return x-1
else:
return -1

⇒次の解答で正解
def simple_match(str1, str2):
for x in range(len(str1)-len(str2)+1):
if str2 == str1[x:x+len(str2)]:
return x
else:
return -1

x = -1
if x < 0:
    print('x is positive')
elif x == 0:
    # no error
    pass
elif 0 < x < 5:
    print('x is positive and smaller than 5')
else:
    print('x is positive and larger than or equal to 5')

⇒内容とは関係ないが、’x is negative’では?

⇒以下の解答を作成したが間違い。ChatGPTによると以下の指摘。
replace の結果を受け取っていない。
[:index-1] だと最後の1文字が欠ける。スライスは後半の数字の1文字前までを疲労から[:index]でよい。
③ 最後の単語で index() がエラーになる。文末が「.」

def collect_engwords(str_engsentence):
list = []
str_engsentence.replace(‘ ‘, ‘,’)
while len(str_engsentence) > 0:
index = str_engsentence.index(‘,’)
strx = str_engsentence[:index-1]
str_engsentence = str_engsentence[index+1:]
if len(strx) > 2 :
list.append(strx)
return list

⇒以下に修正したところ正解。いきなり難しくないですか。

def collect_engwords(str_engsentence):
list = []
str_engsentence = str_engsentence.replace(‘ ‘, ‘,’)
str_engsentence = str_engsentence.replace(‘.’, ‘,’)
while len(str_engsentence) > 0:
index = str_engsentence.index(‘,’)
strx = str_engsentence[:index]
str_engsentence = str_engsentence[index+1:]
if len(strx) > 2 :
list.append(strx)
return list

⇒以下の解答を作成したが間違い。ChatGPTによると以下の指摘。

def swap_lists(ln1, ln2):
list1 = []
list2 = []
if len(ln1) >= len(ln2):
for x in range(len(ln2)/2):
list1.append(ln1[2x])
list2.append(ln2[2x])
list1.append(ln2[2x+1])
list2.append(ln1[2x+1])
else x in range(len(ln1)/2):
list1.append(ln1[2x])
list2.append(ln2[2x])
list1.append(ln2[2x+1])
list2.append(ln1[2x+1])
tuple = (list1, list2)
return tuple

① 2x という書き方は Python ではエラー。正しくは:ln1[2*x]
② range(len(ln2)/2) はエラー(/ は float)。正しくは:range(len(ln2)//2)
③else x は Python の構文として成り立たない。そもそもこの問題は「2つのリストは同じ大きさ」
④list1, list2 を作り直している(破壊しても良いのにコピーしてる)
tuple は組み込み型であり、変数名ではない
⑥そもそも “swap” の意味が違ってる。index = 1,3,5,… の要素を swap するだけ。

⇒①は忘れてた。納得。②も納得。少数の場合、rangeはどう処理されるんだろう、とは思っていた。③も納得。問題読み間違い。④は納得できない。破壊しても良いは破壊しなくても良いってことでは。
⑤は良く分からない。ダメなのか? ⑥も大きなお世話。交換する感じでコマンド書けたらこんな面倒な式は立てない。
以下に修正して一応true。

def swap_lists(ln1, ln2):
list1 = []
list2 = []
if len(ln1) % 2 ==0:
for x in range(len(ln1)//2):
list1.append(ln1[2x])
list2.append(ln2[2x])
list1.append(ln2[2x+1])
list2.append(ln1[2x+1])
else:
for x in range(len(ln1)//2):
list1.append(ln1[2x])
list2.append(ln2[2x])
list1.append(ln2[2x+1])
list2.append(ln1[2x+1])
list1.append(ln1[-1])
list2.append(ln2[-1])
tuple = (list1, list2)
return tuple

模範解答は以下の通り。こんな簡単に行けるとは。
return ln1, ln2 はタプルらしい。(ln1, ln2)と同じ意味
・ln1[j], ln2[j] = ln2[j], ln1[j]は同時代入。こんなの習ったっけ?←2.2リストで多重代入という話があった。
 ln1[j] = ln2[j]の後に、
 ln2[j] = ln1[j]をやるのとは異なる。

⇒ 以下の解答を作ったら正解。 珍しくすんなり解けた。

def count_capitalletters(str1):
CAPITALS = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ’
sum = 0
for x in CAPITALS:
sum += str1.count(x)
return sum

模範解答は以下の通り。私の回答の方がシンプルかも。str2 != str3で記号を除外。

⇒ 以下の解答を作ったら正解。スライスをx+2にしないように注意。

def identify_codons(str_augc):
list = []
for x in range(0,len(str_augc),3):
list.append(str_augc[x:x+3])
return list

模範解答は以下の通り。rangeの置き方が違うくらい。

⇒以下の解答を作成したところ正解。whileを使う時はx=1とx += 1の併用が分かりやすい。len(word)はコンマでwordの長さが変わるから、「-x」を入れている。また、whileの終点も変わると面倒なので、最初に定数tにした。

def add_commas(int1):
word = str(int1)
t = len(word)
x = 1
while 3x < t:
word = word[:len(word)-3x+1-x]+’,’+word[len(word)-3*x-x+1:]
x += 1
return word

模範解答は以下の通り。rangeの3番目を-1にすることで後ろから処理できるとは思いつかなかった。

⇒以下の解答を作成したが間違い。「list1 の要素が文字列でなければ文字列に変換する」という内容に対処できていない。文字列じゃない場合に間違いになる。

def sum_strings(list1):
t = len(list1[-1])+2
str0 = ‘, ‘.join(list1)
if ‘,’ in str0:
str1 = str0[:-t] + ” and” + str0[-t+2:]
else:
str1 = str0
return str1

⇒以下の回答にして正解。len(リスト)でリストの要素の数を数えられる。ifのところは「=」ではなく「==」。「=」だと定義になってしまう。

def sum_strings(list1):
if len(list1) ==1:
str1= str(list1[0])
elif len(list1) == 2:
str1= str(list1[0]) + ‘ and ‘ + str(list1[1])
else:
t = 1
str1= str(list1[0])
while t < len(list1)-1:
str1 = str1 + ‘, ‘ + str(list1[t])
t +=1
str1 = str1 + ‘ and ‘ + str(list1[-1])
return str1

以下が模範解答。模範解答の方が簡潔。

⇒以下の回答にして正解。

def handle_collision2(dic1, str1):
if len(dic1) == 10: #dic1が全て埋まっている時は即終了
return dic1
else:
n = len(str1)
while n >0:
if n not in dic1: #nがキーの数字として無かったら、そこでストップ
break
elif n < 10: #あったらn に1追加
n += 1
else:
n = 1
dic1[n] = str1
return dic1

以下が模範解答。まずgetメソッドを忘れていた。rangeでiにnから順番に11の前(10)まで入れる。return dic1にしなくて良いのか?←dic1 は参照渡しなので変更が反映される、とのこと(ChatGPT)。それ自体を変更する場合はreturnが不要で、新しく作る場合は必要って話だと理解。

以下の回答で正解。すぐできた。

def handle_collision3(list1):
dic1 = {}
n = 0
while n < len(list1):
if list1[n][0] not in dic1:
dic1[list1[n][0]] = list1[n][1]
n += 1
else:
n += 1
return dic1

以下が模範解答。

コメント

タイトルとURLをコピーしました