2023-2I プログラミング1 第10回 講義資料

2023年06月29日(木)5・6時限

1 準備

2 前回の復習: Pythonの実行開発環境の構築

操作上の不明箇所があれば、まずは前回の 第09回講義ノート を参照してください。

  1. ローカル環境に 0629-PG1 という フォルダを新規作成 してください (フォルダ名は任意に設定可) 。
  2. 作成したフォルダを VS Code で開いてください (コンテキストメニューから「Codeで開く」を選択)。
  3. VS Code 内で Ctrl + Shift + @ でターミナル (PowerShell:pwsh) を起動してください。
  4. Pythonの仮想環境を作成し、その仮想環境を 有効化 してください。
    • python -m venv .venv
    • .venv/Scripts/Activate.ps1
  5. pip list で仮想環境にインストールされているPythonライブラリ (Package) を確認してください。次のように最低限のライブラリ (Package) だけがインストールされているはずです。
Package    Version
---------- -------
pip        23.1.2
setuptools 65.5.0
  1. 次のようなPythonプログラム (test01.py) を VS Code 上で作成して、正常に実行できることを確認してください。
items = ['どうのつるぎ','やくそう','どくけしそう','ぬののふく']
for item in items:
  print(item)
  1. ノートブック開発環境 Jupyter を VS Code でも使えるようにするために pip install jupyterjupyter ライブラリを Python仮想環境 にインストールしてください。インストール後は pip list でライブラリが追加されていることを確認してください。
  2. VSCode上で test02.ipynb (拡張子に注意) を作成し、コードセルを追加し、次のプログラムを記述してください。
%reset -f
print('ドラゴンクエストIII そして伝説へ…')
  1. ノートブック編集画面の右上の「カーネルの選択」から適切なもの (Python仮想環境) を選択して、その後、コードセルを実行 (Ctrl+Enterを押下) して、適切な出力が得られることを確認してください。
img

2.1 Google Colab. にプリインストールされているパッケージの確認

Coogle Colab. では 主要なPythonライブラリ (Package ) が予めインストール されています。Coogle Colab. にインストール済みのライブラリを確認するために、Colab.でコードセルを追加して、次のようなコマンドを打ち込んで実行してください。

!pip list

Colab. においては ! からはじまる文は Pythonプログラムではなく OS (Linux) に対するコマンド として扱われます。

3 pipコマンドを使ったライブラリの追加

pipコマンドを使った Pythonライブラリの追加 について学んでいきます。pipは「ピップ」と読みます。

pipとはなにか

pip (ピップ) とは、Pythonの「パッケージ管理システム」のひとつで、Pythonで書かれたパッケージ のインストール/管理を行なうためシステムです。パッケージ (ライブラリ・モジュール) とは、他の人が書いて再利用可能にしたPythonのコードのことを指します。これらのパッケージを使用すると、自分ですべてのコードを書く代わりに、他の人が既に書いたコードを利用することができ、作業を効率化できます。

本授業では利用しませんが、Python開発環境の Anaconda (アナコンダ) では conda というパッケージ管理システムが採用されています。なお、pip と conda を併用すると様々な問題が生じるので注意してください。

3.1 準備

ローカルPC上に構築したノートブック環境 (Jupyter環境) において、コードセルを追加して、次のプログラムをコピペして実行 (Ctrl+Enterを押下) してください。以下のコードは matplotlib というライブラリを利用してグラフを描画 するプログラムになります。

%reset -f
import matplotlib.pyplot as plt

x = [0, 10,20,30,40]
y = [20,40,30,50,40]

fig,ax = plt.subplots()
ax.plot(x,y,marker='o')
plt.show()

実行すると ModuleNotFoundError: No module named 'matplotlib' という実行時エラーが発生します。このエラーメッセージは、上記の第02行目の import に関連して発生しているもので ModuleNotFoundError: ‘matplotlib’ という名前の モジュール (=ライブラリ、パッケージ) が見つかりません という意味です。

現在のPython環境 (=先ほど作成・有効化した仮想環境内) に、matplotlib のライブラリが存在しないために生じているエラーになります。

3.2 現在の開発環境にインストールされているライブラリの確認

念のために「本当に matplotlib が現在のPython環境 (=venvで構築した仮想環境) にインストールされていないのか?」を確認してみましょう。

VS Code のターミナルから、次のコマンドを入力して インストールされているライブラリを一覧表示 してください。

pip list

結果が大量出力されます。このなかから matplotlib の有無を探すのは大変なので、出力結果から mat という文字を含む行だけを抽出して表示します。

次のコマンドを入力してください。

pip list | Select-String -Pattern "mat"

ここで | は「情報1」で学習したように パイプ と呼ばれる記号です。コマンドライン上で、パイプを command1 | command2 のように使用したときは、command1 の出力を command2 の入力に「パイプする」という意味になります。

パイプする」とは、コマンドの実行結果 (文字列の出力) をコンソールに表示するのではなく 別のコマンド対して入力として引き渡す ということを意味します。

ここでは pip list というコマンドの実行結果 (文字列の出力) を、Select-String -Pattern "mat" というコマンドに対する入力として与えるという意味になります。

このコマンドの結果は、次のようになります。

(.venv) PS C:\Users\wada\...\0629> pip list | Select-String -Pattern "mat"

matplotlib-inline        0.1.6
nbformat                 5.9.0

この結果から確かに matplotlib はインストールされていないことが確認できます。

3.3 pip本体の更新

pipコマンドを使って「Pythonライブラリ」をインストールする際には、まずはじめに pip 本体が最新版であることを確認 する必要があります。以下のコマンドにより、pipのバージョンをチェックし、もし古いバージョンであれば最新版に更新すること (アップグレードすること) ができます。

python -m pip install --upgrade pip

もし、現状でpipが最新版であれば、以下のようなメッセージが表示されます ( Requirement already satisfied : 要求は満たされている )。

Requirement already satisfied: pip in c:\users\wada\...\0629\.venv\lib\site-packages (23.1.2)

また、最新版でなかった場合は、、以下のようなメッセージが表示され、pipが最新版に更新されます。特に最終行が「Successfully」となっていることを確認してください。問題があるとエラー関係の表示に出力されます。

Requirement already satisfied: pip in c:\users\wada\...\0629\.venv\lib\site-packages (22.1)
Collecting pip
  Downloading pip-23.1.2-py3-none-any.whl (2.1 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 26.3 MB/s eta 0:00:00
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 22.1
    Uninstalling pip-22.1:
      Successfully uninstalled pip-22.1
Successfully installed pip-23.1.2

このようなアップグレード操作をしておかないと 以降の操作を行なった場合にエラーが発生する可能性があります。この点に注意してください。

3.4 Pythonライブラリのインストール

以下のコマンドで matplot をインストールすることができます。pip installを実行するとインターネット上の PyPI(パイピーアイ、Python Package Index)という Pythonのパッケージのリポジトリ から目的のライブラリを検索し、それをローカル環境にダウンロードしてインストールします。

pipによるライブラリのインストールは、スマートフォンにおいて Playストア や AppStore などのリポジトリからアプリをインストール するイメージです。

pip install matplotlib

上記のコマンドを実行すると matplotlib 以外にも、依存関係のあるライブラリ (matplotlibの動作に必要な別ライブラリ) もあわせてインストールされます。

完了後、再度 pip list | Select-String -Pattern "mat" を実行し、実際にインストールができたか確認してみてください。

3.5 動作確認

VS Codeで作成しているノートブックに戻って、再度、セルを実行してください。今度は ModuleNotFoundError が発生せず、プログラムが実行され、次のようにグラフが出力されるはずです。

img

ModuleNotFoundError

今後、ModuleNotFoundError が発生した場合は、ここで説明した手順で必要なライブラリをインストールしてください。

4 繰返し構文 (少しだけ発展的な利用)

4.1 繰返し構文の基本

ここまで、繰返し構文としては、次のように range1個の引数 (以下の例では 5) を与えるものを取り扱ってきました。

%reset -f
for i in range(5):
  print(i)

このプログラムを実行すると、まずは ループ変数である i0 が格納された状態でブロック (=インデントされた範囲) が実行され、つづいて i1 が格納された状態でブロックが実行され・・・という処理が5回、つまり 変数i4 となるまで処理が繰返されました。

0
1
2
3
4

4.2 演習①

次のプログラムの第03行目を書き換えて、下記に示す「期待する結果」が得られるようにせよ。

%reset -f
for i in range(5):
  print(i) # ここを書き換える

期待する結果

5
6
7
8
9

4.3 rangeに対して「2つの引数」を与える

演習①のような方法で 5 から 9 を得ることはできます。

この方法とは別に range に「開始値」と「終了値+1」の 2つの引数 を与えることでも同様の処理が可能となります。

%reset -f
for i in range(5,9+1):
  print(i)

range に2個の引数を与えた場合、ループ変数 (上記の例では i) には「第1引数の値」から「第2引数の値 未満 の値」が順次格納され、ループ処理されます。

5 繰返し構文 (ループ変数に小数値を使用したい場合)

上記では range の引数により、ループ変数の「範囲 (開始値・終了値) 」や「刻み幅」を(ある程度までは)操作できること学びました。しかし、range では整数値しか扱うことができず、小数値をループ変数したい場合 (特に物理シミュレーションなどの要求) は、次のようにブロック内で別の変数を用意する必要があります。

次のプログラムを実際に実行して結果を確認してください。

%reset -f
# ループブロックで 0.00 から 1.00 まで
# 0.01 刻みの値を使用したい
for i in range(100+1):
  t = 0.01 * i
  print(f'{t:.2f}',end=' ')
  if i%10 == 0: # 0.1単位で改行
    print('')

上記では、割り算の「余り」を求める % 演算子を使用して、一定値に達したときに改行するようにしています。

プログラミングにおいては、可能な範囲で減らせる変数は減らせることが望ましく (使用する変数は必要最低限にすることが望ましく) 、上記のような要求は numpy というライブラリを使うことで解決することができます。

numpy数値計算支援用のライブラリで「ナムパイ」ではなく「ナンパイ」と読みます。工学分野では極めて使用頻度が高いライブラリとなります。具体的には range() の代わりに np.arange() を使うことで、直接的に小数のループ変数を使用することができます。

%reset -f
import numpy as np   # numpy を np という省略名で使用
for t in np.arange(0,1.01,0.01):
  print(f'{t:.2f}',end=' ')
  if t % 0.1 == 0 :
    print()

実際に実行して結果を確認してください。

5.1 演習②-1

上記プログラムを実行すると、次のように期待するような位置で改行がされていない。なぜ、このようなことが発生するのか、分析・考察せよ。また、どのように書き換えれば、期待するような結果が得られるか考えてコードを修正せよ。

0.00 
0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.10 
0.11 0.12 0.13 0.14 0.15 0.16 0.17 0.18 0.19 0.20 
0.21 0.22 0.23 0.24 0.25 0.26 0.27 0.28 0.29 0.30 0.31 0.32 0.33 0.34 0.35 0.36 0.37 0.38 0.39 0.40 
0.41 0.42 0.43 0.44 0.45 0.46 0.47 0.48 0.49 0.50 0.51 0.52 0.53 0.54 0.55 0.56 0.57 0.58 0.59 0.60 0.61 0.62 0.63 0.64 0.65 0.66 0.67 0.68 0.69 0.70 0.71 0.72 0.73 0.74 0.75 0.76 0.77 0.78 0.79 0.80 
0.81 0.82 0.83 0.84 0.85 0.86 0.87 0.88 0.89 0.90 0.91 0.92 0.93 0.94 0.95 0.96 0.97 0.98 0.99 1.00 

5.2 演習②-2

上記のプログラムの第02行目を import numpy as np から import numpy に変更した場合 (=np という省略形を使わない場合)、以降のプログラムをどのように書き換えるべきか。実際にコードを記述して確認せよ。

6 繰り返し構文2 (whileの利用)

ここまでは、繰返し構文として for を学習してきました。for を使った繰返し構文は、繰返しの回数が予め決まっている場合 に使いやすいものとなっています。

一方で、繰返し回数が決まってなく、特定の条件が満たされるまで繰返し処理したい場合には while による繰返し構文が適しています。

while の一例を示します。次のプログラムをコピペして実行し、その後、その実行結果とプログラムを照らし合わせて内容を理解してください (重要なポイントになります、読み飛ばさず、確実に理解してください) 。理解できないところは「Python while 初心者向け」などでウェブ検索して解決してください。

%reset -f
import time # 第12行目で time.sleep(3) を使用する準備
import random as r
print('九九の練習プログラムです')
input('はじめるためには [Enter] を押下してください。')
x = 1 # 処理継続フラグを立てる

while x == 1 : # 処理継続フラグ変数 x が 1 である間ずっと (while)
  a = r.randint(1,9) # 1~9の範囲のランダムな整数値
  b = r.randint(1,9)
  print(f'問題:{a} x {b} = ? (3秒後に答えが表示されます)')
  time.sleep(3)  # 3秒間プログラムを停止
  print(f'答えは「{a*b}」でした。')
  u = input('つづける場合は [Enter]、終了する場合は 何か文字を入力して [Enter] > ')
  if u != '' :
    x = 0 # 処理継続フラグを折る
print('お疲れ様でした。')

while では、条件式が成立する間、ブロック内の処理が継続されます。条件式は if と同じように while x < 10 :while -10 <= x <= 10 :while x != y : のように記述することができます。

while 構文では、初回も条件式が成立するかチェックされます。そのため、条件の与え方によっては、1回もループ処理されない可能性があります。

6.0.1 演習③

while による繰返し構文を利用して、次のような値を出力するプログラムを作成せよ。

5 6 7 8 9 10 11 12 13 14 15 

ヒント: 次のプログラムを参考にせよ。

%reset -f
i = 0
while i < 10 :
  print(i)
  i+=1 # 「i=i+1」と同じ意味

6.1 無限ループ

while 1 : または while True : のようにすると無限ループとなります。無限ループから抜けだすためには break 文を使用します。

次のプログラムをコピペして実行し、その後、その実行結果とプログラムを照らし合わせて内容を理解してください。

%reset -f
import random as r
import time
wallet = 20_000 # 所持金
bet = 300       # ガチャ1回の費用
n = 1
# SSRゲット or 破産するまでの無限ループッ
while True :
  print(f'{n}回目の{bet}円ガチャに挑戦',end='')
  wallet -= bet  #「wallet=wallet-bet」と同じ
  for i in range(3):
    time.sleep(0.5)
    print('.',end='')
  x = r.choices(['R','SR','SSR'],weights=(75,20,5))[0]
  print(f'{x}を入手! 残金は{wallet:,}{"っ"*r.randint(1,5)}!')
  if wallet < bet or x == 'SSR' :
    break
  n += 1   # 「n=n+1」と同じ 

6.1.1 補足: random.choices

random.choices は、次のように結果を必ずリストとして返してきます。リストではなく、リストの中身が必要な場合は [] で要素を取得してください。

%reset -f
import random as r

# キーワード引数 k=4
x =  r.choices(['R','SR','SSR'],weights=(75,20,5),k=4)
print(x) # => ['R', 'SR', 'R', 'R']

# キーワード引数 k=2
x =  r.choices(['R','SR','SSR'],weights=(75,20,5),k=2)
print(x) # => ['R', 'R']

# キーワード引数 k=1
x =  r.choices(['R','SR','SSR'],weights=(75,20,5),k=1)
print(x) # => ['R']

# キーワード引数 kを省略(k=1)と同じ
x =  r.choices(['R','SR','SSR'],weights=(75,20,5))
print(x) # => ['R']

# キーワード引数 kを省略(k=1)と同じ
x =  r.choices(['R','SR','SSR'],weights=(75,20,5))
x = x[0] # xはリストなので x[0] でゼロ番目の要素を取得
print(x) # R

# 省略形
x =  r.choices(['R','SR','SSR'],weights=(75,20,5))[0]
print(x) # R

6.2 演習③

自キャラと敵キャラによる1対1の自動バトル (交互に攻撃して決着がつくまで戦う系) を実況するようなプログラムを作成せよ。なお、詳細に作りこむととキリがないので、授業中は15分程度で次のセクションに移ること。

作成例

勇者ヨシヒコの前にスライムがあらわれた。
勇者ヨシヒコのHP「50」、スライムのHP「8」。

勇者ヨシヒコの攻撃、スライムに3のダメージ!! スライムのHP 8 -> 5
スライムの攻撃、勇者ヨシヒコに2のダメージ!! 勇者ヨシヒコのHP 50 -> 48
勇者ヨシヒコの攻撃、スライムに3のダメージ!! スライムのHP 5 -> 3
スライムの攻撃、勇者ヨシヒコに・・・

7 リスト操作の負荷量の比較

前々回の講義ではリストを初期化する方法 (例えば [0]*10 など) や、リストに要素を追加する方法 (例えば appendinsert メソッドなど) を学習しました。

基本的に、リストに順次要素を追加する方法は、いわゆる「重たい処理」となります。あらかじめリストの長さが決まっているケースでは、その長さのリストを作成しておき、そこに値を書き込んでいくほうが高速かつ効率的な処理となります。

例として、長さが「1000」で、その要素として先頭から 0、1、2、…、999 という数値が格納されるリストを生成すること考えます。この処理を次の3つの方法で実行して、それに要する時間を %%timeit で計測してみます。

%%timeit は既に過去の紹介していますが、セル単位の実行時間を計測するマジックコマンド (Colab.環境専用) です。なお、%%timeit のセルのなかでは %reset -fprint は使わないようにしてください。

次の3つの処理の処理時間を比較します。Colab.環境で上記の各プログラムを実行して、その実行時間を比較してみてください。

  1. 初期値が 0 で、要素数 1000 のリストを作成する (arr=[0]*1000) 。その後、for 構文で、先頭から順番に要素の値を書き換えていく。
  2. 要素数が 0 の空のリストを作成する (arr=[])。その後、for 構文のなかで、リストの末尾に順次要素を追加していく。
  3. 要素数が 0 の空のリストを作成する (arr=[])。その後、for 構文のなかで、リストの先頭に順次要素を追加していく。
%%timeit
arr = [0]*1000
for i in range(1000):
  arr[i]=i
%%timeit
arr = []
for i in range(1000):
  arr.append(i)
%%timeit
arr = []
for i in range(1000):
  arr.insert(0,999-i)

特に insert を使ってリストの途中 (末尾以外) に要素を追加する処理は、処理時間が長くなることが確認できると思います。

8 リスト要素の削除

リストは appendintsert などのメソッドにより「要素の追加」が可能でした。これに対して popremove などのメソッドにより「要素の削除」が可能です。

リストの要素削除について動作を確認するために、次のようなリストを考えます。実行して結果を確認してください。

%reset -f
arr = list('ABCDBBEF') # 文字列からリストを作成可能
print(arr)      # => ['A', 'B', 'C', 'D', 'B', 'B', 'E', 'F']
print(len(arr)) # => 8

上記のリスト arr に対して要素削除の操作を行ないます。

pop は「指定したインデックスの要素」を取り除き、その部分を「前詰め」します。例えば、arr から 'C' を取り除きたいときは、次のプログラムのように、そのインデックスである 2pop の引数に指定します。pop メソッドには 戻り値 があり、取り除いた値を受けとることもできます。

実際に次のプログラムを実行して、結果を確認してみてください。

%reset -f
arr = list('ABCDBBEF')
p = arr.pop(2)
print(arr)      # => ['A', 'B', 'D', 'B', 'B', 'E', 'F']
print(len(arr)) # => 7
print(f'p={p}') # => p=C

上記の例では、第03行目で変数 parr.pop(2) の結果を受け取っていますが、これは、次のように受け取らないことも可能です。

%reset -f
arr = list('ABCDBBEF')
arr.pop(2) # 戻り値を受け取らない
print(arr)

上記のプログラムでは、取り除きたい要素 'C' のインデックスを 2 のように数値で直接的に指定しました。しかし、実施にはそのようなケースは少なく、前々回で学習した index メソッドと組み合わせて利用します( index は指定の要素が「リストの何番目に存在するかを調べるメソッドでした)。

つまり、次のように利用します。

%reset -f
arr = list('ABCDBBEF')
idx = arr.index('C') # Cのインデックスを得る
arr.pop(idx)
print(arr)

これは、さらに次のように変数 idx を省いて記述することができます。可読性が損なわれない範囲で、不要な変数は省くことが望ましいです。

%reset -f
arr = list('ABCDBBEF')
arr.pop(arr.index('C')) # ここに注目
print(arr)

8.1 remove による要素の削除

remove では、インデックスではく、削除したい要素そのものを指定してリストから取り除くことができます。先ほどと同様に 'C' を削除したい場合は次のようにします。

%reset -f
arr = list('ABCDBBEF')
arr.remove('C') # 削除したいものを直接指定
print(arr)

remove では、リスト内で見つかった最初の要素のみ(1個のみ) を削除します。全てを削除したい場合は while などと組み合わせて使用する必要があります。

次のプログラム実行し、その結果を確認してください。

%reset -f
arr = list('ABCDBBEF')
arr.remove('B') # 最初に発見したBのみを削除
print(arr)

8.2 演習④

次のプログラムが意図する動作をするようにコードを追加せよ。

%reset -f
import random as r

target = 'やくそう'
items = ['どうのつるぎ','やくそう','どくけしそう','ぬののふく', 'やくそう', 'やくそう', 'かわのたて']

print('勇者ヨシヒコの所持品 : ',end='')
print(*items, sep=' ')
print()

print(f'勇者ヨシヒコはパルプンテを唱えた。なんと全ての「{target}」が粉々に砕け散った。')
print()

# ここに items からすべての target を取り除く処理を記述する

print('勇者ヨシヒコの所持品 : '

リストのアンパック

リストを関数の引数として与える場合、先頭にアスタリスクをつけて アンパック (展開) という処理を施すことができます。これにより、次のようなことができます。

%reset -f
items = ['どうのつるぎ','やくそう','どくけしそう','ぬののふく']
print(*items, sep='\n') # itemsの頭に「*」を付けた。また sep 引数を設定

このとき、第03行目のは次のような文と同じ意味を持ちます。

print('どうのつるぎ','やくそう','どくけしそう','ぬののふく', sep='\n')

つまり、リストをアンパックすると「リスト内の要素を個別の引数として関数に与えること」が可能になります。

9 宿題

次回は、次のような「斜方投射」の簡易シミュレーションを扱います。斜方投射 (基礎物理学) について復習し、また、下記のプログラムについて読解を試みてください (分からなくても60分は費やしてください) 。

img
%reset -f
import math
import numpy as np
import matplotlib.pyplot as plt

# パラメータ設定
v0 = 20.0    # 初速度(m/s)
theta = 60   # 水平方向から上方にθ(deg)
g = 9.8      # 重力加速度m/s^2

# シミュレーション
dt = 0.1  # 位置計算の時間間隔
iter = 80 # シミュレーション繰返し回数

theta = math.radians(theta) # deg -> rad

arr_x=[0]*iter # X位置を格納するリストの初期化
arr_y=[0]*iter # Y位置 〃

arr_x[0] = x = 0  # Xの初期位置
arr_y[0] = y = 0  # Yの 〃
vx = v0 * math.cos(theta) # X成分の初期速度 
vy = v0 * math.sin(theta) # Y成分 〃

# シミュレーション
for i in range(1,iter):
  vy = vy - g * dt # Y方向の速度更新
  x = x + vx * dt  # X位置の計算
  y = y + vy * dt  # Y位置 〃

  # 地面衝突の反発 (バウンド) の処理
  if y < 0:
    y = 0
    vy = -vy * 0.8  # 反発係数

  arr_x[i] = x
  arr_y[i] = y

# 可視化(現状でここから先は理解不要)
fig,ax = plt.subplots(dpi=120)
ax.scatter(arr_x,arr_y,marker='o',s=20,alpha=0.5)
ax.axhline(0,c='black',lw=0.5)
ax.set_aspect('equal', adjustable='box')
ax.set_ylim(-1,20)
plt.show()

10 課題04の解答例

10.1 問題

FC版DQ3 (ファミコン版のドラゴンクエスト3) の武器を扱った「100連ガチャ」のシミュレータを作成したい。ガチャにより排出される武器は item_names_str でカンマ区切りで与えられる全33種の武器である。また、その排出比は item_p_str でカンマ区切りで与えた数値となっている。

ここでの排出比とは・・・例えば「ひのきのぼう」「こんぼう」「どうのつるぎ」の全3種を仮定し、その排出比が「\(10\)」「\(5\)」「\(2\)」であれば、「ひのきのぼう」があたる確率は \(10/(10+5+2)\)、「こんぼう」があたる確率は \(5/(10+5+2)\)、「どうのつるぎ」があたる確率は \(2/(10+5+2)\) のように考えること。

%reset -f
item_names_str = 'ひのきのぼう,こんぼう,どうのつるぎ,せいなるナイフ,くさりがま,とげのむち,まどうしのつえ,てつのやり,どくばり,てつのつめ,はがねのつるぎ,てつのオノ,あまぐものつえ,いかづちのつえ,さざなみのつえ,さばきのつえ,おおばさみ,ゆうわくのけん,りりょくのつえ,おおかなづち,はやぶさのけん,ゾンビキラー,ドラゴンキラー,くさなぎのけん,ガイアのつるぎ,ふぶきのつるぎ,いなづまのけん,まじんのオノ,らいじんのけん,もろはのつるぎ,はかいのつるぎ,おうごんのつめ,おうじゃのけん'
item_p_str = '50,50,50,40,40,35,30,30,25,20,20,20,10,10,10,10,10,10,10,10,5,5,5,4,3,3,2,2,2,2,2,2,1'

上記の設定に基づき100連ガチャのシミュレーションを実行し、次のような出力を得たい。

100連ガチャの結果、以下のアイテムを獲得しました。
ひのきのぼう ... 8個
こんぼう ... 8個
どうのつるぎ ... 9個
せいなるナイフ ... 8個
くさりがま ... 11個
とげのむち ... 7個
まどうしのつえ ... 5個
てつのやり ... 7個
どくばり ... 6個
てつのつめ ... 5個
はがねのつるぎ ... 4個
てつのオノ ... 3個
あまぐものつえ ... 2個
いかづちのつえ ... 1個
さざなみのつえ ... 6個
さばきのつえ ... 2個
ゆうわくのけん ... 3個
りりょくのつえ ... 2個
おおかなづち ... 1個
ゾンビキラー ... 1個
まじんのオノ ... 1個

この要求を満たすことができるPythonプログラムを考え、実装せよ。

10.2 解答例①

%reset -f
%reset -f
import random as r

item_names_str = 'ひのきのぼう,こんぼう,どうのつるぎ,せいなるナイフ,くさりがま,とげのむち,まどうしのつえ,てつのやり,どくばり,てつのつめ,はがねのつるぎ,てつのオノ,あまぐものつえ,いかづちのつえ,さざなみのつえ,さばきのつえ,おおばさみ,ゆうわくのけん,りりょくのつえ,おおかなづち,はやぶさのけん,ゾンビキラー,ドラゴンキラー,くさなぎのけん,ガイアのつるぎ,ふぶきのつるぎ,いなづまのけん,まじんのオノ,らいじんのけん,もろはのつるぎ,はかいのつるぎ,おうごんのつめ,おうじゃのけん'
item_p_str = '50,50,50,40,40,35,30,30,25,20,20,20,10,10,10,10,10,10,10,10,5,5,5,4,3,3,2,2,2,2,2,1,1'

item_names = list(item_names_str.split(','))   # 文字列→リスト
item_p = list(map(int, item_p_str.split(','))) # 文字列→リスト

assert len(item_names) == len(item_p) # 2つのリストの長さが同じであることを確認

item_counts = [0]*len(item_names) # 結果を格納する0で初期化したリスト
n = 100
for t in range(100):
  item = r.choices(item_names,weights=item_p,k=1)[0]
  index = item_names.index(item)
  item_counts[index]+=1

print(f'{n}連ガチャの結果、以下のアイテムを獲得しました。')
for i in range(len(item_names)):
  if item_counts[i] != 0: # 1個以上獲得していれば
    print(f'{item_names[i]} ... {item_counts[i]}個')

10.3 解答例②

%reset -f
import random as r

item_names_str = 'ひのきのぼう,こんぼう,どうのつるぎ,せいなるナイフ,くさりがま,とげのむち,まどうしのつえ,てつのやり,どくばり,てつのつめ,はがねのつるぎ,てつのオノ,あまぐものつえ,いかづちのつえ,さざなみのつえ,さばきのつえ,おおばさみ,ゆうわくのけん,りりょくのつえ,おおかなづち,はやぶさのけん,ゾンビキラー,ドラゴンキラー,くさなぎのけん,ガイアのつるぎ,ふぶきのつるぎ,いなづまのけん,まじんのオノ,らいじんのけん,もろはのつるぎ,はかいのつるぎ,おうごんのつめ,おうじゃのけん'
item_p_str = '50,50,50,40,40,35,30,30,25,20,20,20,10,10,10,10,10,10,10,10,5,5,5,4,3,3,2,2,2,2,2,1,1'

item_names = list(item_names_str.split(','))   # 文字列→リスト
item_p = list(map(int, item_p_str.split(','))) # 文字列→リスト

assert len(item_names) == len(item_p) # 2つのリストの長さが同じであることを確認

items = r.choices(item_names,weights=item_p,k=100)
for item in set(items):
  print(f'{item} ... {items.count(item)}個')