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

2024年06月28日(金)1・2時限

1 準備

2 前回の復習: Python環境の構築 ( 目標時間: 15分)

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

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

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

GoogleColab.では 主要な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\...\PG-10> pip list | Select-String -Pattern "mat"

matplotlib-inline        0.1.7
nbformat                 5.10.4

この結果から確かに 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\...\PG1-10\.venv\lib\site-packages (24.1.1)

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

Requirement already satisfied: pip in c:\users\...\PG1-10\.venv\lib\site-packages (24.0)
Collecting pip
  Downloading pip-24.1.1-py3-none-any.whl.metadata (3.6 kB)
Downloading pip-24.1.1-py3-none-any.whl (1.8 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 10.5 MB/s eta 0:00:00
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 24.0
    Uninstalling pip-24.0:
      Successfully uninstalled pip-24.0
Successfully installed pip-24.1.1

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

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" を実行し、実際にインストールができたか確認してみてください。

matplotlib                3.9.0
matplotlib-inline         0.1.7
nbformat                  5.10.4

上記より、matplotlib のバージョン 3.9.0 が仮想環境にインストールされたことが分かります。

3.5 動作確認

VSCodeで作成しているノートブックに戻って、再度、セルを実行してください。今度は 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 演習1 ( 目標時間: 3分)

次のプログラムの 第03行目 を書き換えて、下記に示す「期待する結果」が得られるようにしてください。第02行目 を書き換えることは不可とします。

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

期待する結果

5
6
7
8
9

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

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

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

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

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

4.4 演習2 ( 目標時間: 15分)

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('')

上記では、割り算の 余り を求める % 演算子を使用して、一定値に達したときに改行するようにしています。なお、% 演算子については第03回講義で学びました。

プログラミングにおいては、使用する変数は必要最低限にすることが望ましくです。上記のような要求は 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 演習3-1 ( 目標時間: 10分)

上記プログラムを実行すると、次のように期待するような位置で改行がされていません。

なぜ、このようなことが発生するのか、分析・考察せよ。また、どのように書き換えれば、期待するような結果が得られるか考えてコードを修正してください。

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 演習3-2 ( 目標時間: 8分)

上記のプログラムの 第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('お疲れ様でした。')

Jupyterでinputを使用する場合の注意

前回の講義でも解説したように、Jupyter環境では input() に対する入力受付は、画面上部に表示されます。

img

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

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

6.1 演習4 ( 目標時間: 5分)

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.2 無限ループ

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.2.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.3 演習4 ( 目標時間: 15分)

自キャラと敵キャラによる「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 構文で、先頭から順番に要素の値を書き換えていく。以下の p1.py 参照。
  2. 要素数が 0 の空のリストを作成する (arr=[])。その後、for 構文のなかで、append でリストの「末尾」に順次要素を追加していく。以下の p2.py 参照。
  3. 要素数が 0 の空のリストを作成する (arr=[])。その後、for 構文のなかで、insert でリストの「先頭」に順次要素を追加していく。以下の p3.py 参照。
%%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 などと組み合わせて remove 使用する必要 があります。

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

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

8.2 演習5 ( 目標時間: 10分)

次のプログラムが意図する動作をするようにコードを追加してください。

%reset -f
import random as r

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

print('勇者ヨシヒコの所持品 : ',end='')
print(*items, sep=' ') # アンパック(後述)
print()

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

#【意図する動作】
# ここに items からすべての target を取り除くような処理を記述する

print('勇者ヨシヒコの所持品 : ',end='')
print(*items, sep=' ') 
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()