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

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

1 準備

2 PG1-課題02の解答例①

Google Colab. ノートブックにあわせて最小限の修正を加えています (%reset -fの追加など)。

2.1 自キャラを基準とした敵キャラの距離と方向の計算

%reset -f
import math
# x1 = float(input()); y1 = float(input())
# x2 = float(input()); y2 = float(input())
x1, y1 = 2.3,   2
x2, y2 = -1 , 4.5
d = ((x1-x2)**2+(y1-y2)**2)**0.5 # 距離計算(三平方の定理)
r = math.degrees( math.atan2((x2-x1),(y2-y1)) ) # 角度計算
print(f'{d:.2f}')
print(f'{r:.1f}')

べき乗の計算には ** 演算子を使用します。また \(\sqrt{x}\) は、1年次の「基礎数学」で学んだように「 \(\sqrt{x}=x^{\frac{1}{2}}=x^{0.5}\) 」であるため x**0.5 (またはx**(1/2)) で計算できます。

なお ** 演算子の使用を推奨しますが、別の方法として import math をしたうえで math.sqrt(x)平方根を計算したり、math.pow(x,5) のようにべき乗を計算したりできます。

**演算子の優先順位

** 演算子の優先順位にしてください。確信を持てない場合は、その都度確認するか、括弧をつけて使用してください

a1 = 2**2*3   # -> (2**2)*3 -> 12
a2 = 2**(2*3) # -> 64
b1 = 2**1/2   # -> (2**1)/2 -> 1
b2 = 2**(1/2) # -> 1.41..

確認: 各変数に格納される値はどのようになるか。予想して確認せよ (一般に、このようなケースでは括弧を明示するべきである)。

c1 = 5*2**2
c2 = 4/2**2

中級者向け:自力で平方根を求める

** 演算子や、mathモジュール (ライブラリ) を使わなくとも平方根を計算できます。以下に「ニュートン法」という手法により、繰返し計算から平方根を求める方法を示します。もちろん非推奨です。

%reset -f
import math
n = 10 # 10の平方根を求める

# 繰返し処理で平方根を計算する
sqrt_n = n / 2
for i in range(4): # 繰返し計算
  q = (sqrt_n + n / sqrt_n) / 2.0
  sqrt_n = q

print(f'sqrt({n})= {sqrt_n:.20f} ライブラリを未使用')
print(f'sqrt({n})= {math.sqrt(n):.20f} ライブラリを使用')
print(f'sqrt({n})= {n**0.5:.20f} **演算子を使用')

プログラムの実行結果は次のようになります。

sqrt(10)= 3.16227766044413627355 ライブラリを未使用
sqrt(10)= 3.16227766016837952279 ライブラリを使用
sqrt(10)= 3.16227766016837952279 **演算子を使用

** 演算子やmathライブラリを使った場合と結果を比較してください。

3 利用頻度が高いmathモジュールの関数

mathモジュール (ライブラリ) のなかでも、次の関数は利用頻度が高いので覚えておいてください。

このほかにも、mathモジュールには様々な関数が存在します。それらの一覧や詳細は リファレンス で確認してください。

組合せ

1年次の「基礎数学B」で学習済みの (はず) ですが「 \(n\) 個の異なるものから \(r\) 個を選ぶ組み合わせの総数」を \(_{n}\mathrm{C}_{r}\) のように表記します。

\[ {}_{n}\mathrm{C}_{r} = \frac{n!}{r!(n-r)!} \]

なお、上記の式はTeXでは $$ {}_{n}\mathrm{C}_{r} = \frac{n!}{r!(n-r)!} $$ のように表記します。

3.1 演習①

確率 \(p\) の事象が、\(n\) 回の試行で \(k\) 回発生する確率 \(P\) について、次式 (二項分布) で計算できることが数学的に知られている (詳しくは4年次の「確率統計」の授業で学びます) 。

\[ P={}_{n} \mathrm{C}_{k} \cdot p^{k}\cdot(1-p)^{n-k}\]

例えば「あたり」の割合が \(15\%\) のクジを \(5\) 回引いたときに、\(1\) 回だけ「あたり」がでる確率 \(P_{1}\) は次のようになる。

\[ P_{1}={}_{5} \mathrm{C}_{1} \cdot 0.15^{1}\cdot(1-0.15)^{5-1} \fallingdotseq 0.392\]

同様に、\(2\) 回だけ「あたり」がでる確率 \(P_{2}\) は次のようになる。

\[ P_{2}={}_{5} \mathrm{C}_{2} \cdot 0.15^{2}\cdot(1-0.15)^{5-2} \fallingdotseq 0.138\]

また、\(1\) 回 あるいは \(2\) 回の「あたり」がでる確率は、\(P_{1}+P_{2}\) で計算できる。


以上を踏まえて、以下の問いに答えよ。その際、必ずPythonプログラムを利用して計算すること暗算や算盤 (そろばん) の使用は禁止とする

あるスマホゲームのガチャにおいて「メタスラの剣」というアイテムの提供割合(排出率)は \(0.5\%\) となっている。\(1\) 回のガチャには \(300\) ジェムが必要である。また \(10,000\) 円で \(11,720\) ジェムが購入できる。

なお、取り組みにあたっては 次の点に注意 すること。

4 利用頻度が高い (数学系の) 組み込み関数

組み込み関数とは、import の宣言をせずに利用できる関数で「標準関数」や「ビルトイン関数」とも呼ばれます。既に何度も利用している printinput は組み込み関数になります。

ここでは、利用頻度が高い 数学系 の組み込み関数について記載します。

このほかにも、様々な組み込み関数が存在します。それらの詳細は リファレンス で確認してください。

4.1 とても重要なこと

「教わったこと」を教わった範囲で無条件に受け入れて終わるのではなく 好奇心や興味関心を持って、発展的に「試す」「調べる」「考える」に取り組めるように なってください。 例えば、次のような「疑問」や「興味」が湧いて、それに対して「試す」「調べる」「考える」ができるようになってください。そういった習慣や思考回路になった人が、最終的に (4年生になる頃に) 高いプログラミングを身に付けることができます。

5 PG1-課題02の解答例②

Google Colab. ノートブックで動作するように最小限の修正を加えています。

5.1 直線移動する自キャラのn秒後位置の計算

%reset -f
x1 = float(input()); y1 = float(input())
x2 = float(input()); y2 = float(input())
t = float(input())
# x1, y1 = 6.25, 10.75
# x2, y2 = 42.19, -30
# t = 15
vx = x2-x1; vy = y2-y1 # 自キャラ→目的地のベクトルのX成分とY成分
dis = (vx**2+vy**2)**0.5  # 2点間の距離(ベクトルの大きさ)
ux = vx/dis; uy = vy/dis  # 単位ベクトル

# t秒後の位置は、現在位置 + t秒間に進む距離
ptx = x1 + ux*t
pty = y1 + uy*t
print(f'({x1+ux*t:.2f},{y1+uy*t:.2f})')

ベクトル・行列」の授業で習っているように、単位ベクトルとは「大きさが1のベクトル」です。10行目のように、ベクトルを「ベクトルの大きさ」で割ることで、「単位ベクトル」を求めることができます。

問題設定では「移動速度は \(1\)単位距離/秒 とする」としているので、\(1\) 秒後には現在地点に「単位ベクトル」を加えた地点に移動します。そして、13行目・14行目のように、\(t\) 秒後には現在地点に「単位ベクトルを \(t\) 倍したベクトル」加えた 地点に移動します。

5.2 三角関数を利用して単位ベクトルを計算する方法

math.atan2()math.sin()math.cos() を使って「単位ベクトル」を求めることもできます。

%reset -f
import math
x1, y1 = 6.25, 10.75
x2, y2 = 42.19, -30
vx = x2-x1; vy = y2-y1
theta = math.atan2(vy,vx)
ux = math.cos(theta)
uy = math.sin(theta)
print(f'単位ベクトル ({ux:.2f},{uy:.2f})')

実際に実行して ベクトルを大きさで除して求めた場合と結果が一致する<こと を確認してください。

6 複数の文を1行に記述

通常、Pythonでは 1行に1文を記述 しますが、C/C++言語やJavaScriptのように、セミコロン (;) で区切れば 1行に複数の文を記述 できます。XとYのようにペアになっているようなものは 1行にまとめて書いたほうが分かりやすい場合 もあります。必要に応じて使用してください。

例えば、次の2つのプログラムは 実質的に同じプログラム になります。

x = input()
y = input()
z = input()
x = input(); y = input(); z = input()

7 アンパック (多重代入・展開)

上に示したものと 似て非なるもの に「アンパック (多重代入や展開とも呼ばれます) 」があります。

これには、セミコロンではなく、カンマ , を使用します。アンパックは、この先の授業で登場する リストタプル深く関連した機能となります。詳細については、以降の授業で解説します。

例えば、次の2つのプログラムは、実質的に同じプログラムになります。

x = 10
y = 20
z = 30
x, y, z = 10, 20, 30

セミコロンを使って1行に複数の文を記述することは可読性を損なわない限りはどんな場面でも使用できます。一方で、アンパックは 変数に値を代入 (バインド) するケースでしか利用できません (使用するべきではありません)

8 繰り返し構文におけるループ変数の参照

次のような C/C++言語プログラム をコンパイルして実行すると…

// これはC/C++言語のプログラムです。
#include<stdio.h>
main(){
  for(int i=0; i<5; i++){
    printf("i=%d\n",i);
  }
}

次のような出力を得ることができます。

i=0
i=1
i=2
i=3
i=4

Pythonでは、次のようなプログラムで同様の出力を得ることができます。Colab.ノートブックに貼り付けて、実際に同じ出力が得られることを確認してください。

for i in range(5):
  print(f'i={i}')

ここで、変数 i を「ループ変数」や「ループカウンタ」と言います。range(5) のとき、i はループ毎に 01234 に変化します。ゼロオリジン (ゼロから開始、zero-based) であるため 最終値が 5 ではなく 4 になる点 に注意してください。

8.1 ループ変数の参照

ループ変数は、繰返し構文の内部で参照して (=読み取りして) 計算式に利用したり、関数の引数に与えたり することができます。

%reset -f
import math
for i in range(5):
  a = i + 5         # ループ変数 i を計算式に利用
  b = math.sqrt(i)  # ループ変数 i を関数の引数に利用
  print(f'i={i}, i+5={a}, sqrt{i}={b:.2f}') # i+5 を出力
print('---')    # for終了
print(f'i={i}') # for終了後も参照可能(最終値が格納されている)

8.1.1 演習②

次のような出力を得たい。

X座標 : -4
X座標 : -3
X座標 : -2
X座標 : -1
X座標 :  0
X座標 :  1
X座標 :  2
X座標 :  3
X座標 :  4

次のプログラムの 2行目4行目 を書き換えよ (まずは 書き換えない状態で実行して動作確認して、そのあとに書き換えに取り組むこと)。

%reset -f
n = 5   # ここを書き換え
for i in range(n):
  x = i # ここを書き換え
  print(f'X座標 : {x:>2}')

8.1.2 演習③

非負の整数値を格納する変数 n の値が \(N\) のとき、\(-N\)\(-N+1\)\(\cdots\)\(0\)\(\cdots\)\(N-1\)\(N\) のような数列を得るようなプログラムを作成せよ。

例えば、n\(3\) のときは次のような文字列を出力したい (1文字のスペース区切り、正の値にはプラスの符号がついている点には注意せよ)。

-3 -2 -1 0 +1 +2 +3

例えば、n\(5\) のときは次のような文字列を出力したい。

-5 -4 -3 -2 -1 0 +1 +2 +3 +4 +5

次のプログラムに追記する形式で作成すること。

%reset -f
#@markdown 0より大きい整数値を与えてください 
n = 3    #@param {type:"slider", min:1, max:20, step:1}
# ここから先のコードを記述

8.2 等差数列の生成

初項 \(a\)、公差 \(d\)等差数列の第 \(1\) 項から第 \(n\) 項は、次のプログラムで生成できます。

%reset -f
a = 3  # 初項
d = 4  # 公差
n = 8  # 第n項まで出力
for i in range(n):
  print( a+d*i ,end=' ')

ループ変数 i はゼロオリジン (最初の値が \(0\) である) ため、数学の教科書に記載されているような式 (1オリジン、one-based) を参照する際には読み替えが必要です。

\[ a_{n} = a_{1} + (n-1)d \]

8.2.1 フォーム機能を使用して整形

上記プログラムを Colab.のフォーム機能 を使って整形・装飾したプログラム例を示します。Colab.ノートブックに貼り付けて実行してみてください。

%reset -f
#@markdown # **等差数列の出力** 
#@markdown **初項** $a$ を入力してください。 
a = 3  # @param {type:"number"}
#@markdown **公差** $d$ を入力してください。 
d = 4  # @param {type:"number"}
#@markdown 第何項の数列を出力するか設定してください。 
n = 8 #@param {type:"slider", min:1, max:20, step:1}
for i in range(n):
  print( a+d*i ,end=' ')

8.3 等比数列の生成

初項 \(a\)、公比 \(d\)等比数列の第 \(1\) 項から第 \(n\) 項は、次のプログラムで生成できます。

%reset -f
a = 3  # 初項
r = 2  # 公比
n = 8  # 第n項まで出力
for i in range(n):
  print( a*r**i,end=' ')

等差数列と同様に、数学の教科書に記載されている式を利用する場合は「0オリジン」と「1オリジン」の違いに注意してください。

\[ a_{n} = a \cdot r^{(n-1)} \]

8.3.1 演習④

上記の等比数列生成プログラムColab.のフォーム機能 を使って整形・装飾したプログラムに変更せよ。また、その動作確認をせよ。

8.4 累積値の計算

for文は 累積値 を計算する場合にも利用されます。

例えば、次式のように定義される \(S\) は、

\[ S = 1^2 + 2^2 +3^2 +4^2 + 5^2 \]

以下のようなプログラムで計算ができます。

%reset -f
S = 0  # 初期値0
for i in range(5):
  S = S + i**2
print(f'S={S}')

上記の04行目の S = S + i**2S += i**2 のように省略表記できます

なお S =+ i**2 では意図した動作をしないので注意してください。+= のような演算子を「累算代入演算子」といいます。

8.4.1 演習⑤

あるスマホゲームのガチャにおいて「URアイテム」の提供割合(排出率)は \(0.25\%\) となっている。

\(300\) 回のガチャをしたとき、「URアイテム」の入手個数が \(0\) 個の確率(\(\%\))、入手個数が \(1\)以上の確率(\(\%\))、\(2\)以上の確率(\(\%\))、\(3\)以上の確率(\(\%\))、\(4\)以上の確率(\(\%\)) を求めよ。

9 数学に基づく計算 v.s. PGによるゴリ押し計算

微分積分1」の授業では、等比数列の第 \(1\) 項から第 \(n\) 項までの和 \(S_{n}\) が、次式で計算できることを学びました (数学的な証明についても示されたはずです) 。

\[ S_{n} = \frac{a(1-r^{n})}{1-r}= \frac{a(r^{n}-1)}{r-1}\]

一方で、プログラムで繰返し構文を使ったシンプルなゴリ押し計算でも \(S\) を求めることができます。

両者について比較・考察してみたいと思います。例として、初項 \(a=16\)、公比 \(r=-1/2\) の初項 (第 \(1\) 項) から第 \(50\) 項までの和 \(S_{50}\) を求めてみます。

9.1 計算の正確度を比較

Colab. のノートブックを使って実際に、2つのプログラムを実行して、その結果を確認してみてください。

%reset -f
a = 16
r = -1/3
n = 50
S = a*(r**n-1)/(r-1)
print(f'S = {S:.20f}')
%reset -f
a = 16
r = -1/3
n = 50
S = 0
for i in range(n):
  S += a*r**i
print(f'S = {S:.20f}')

非常に小さな値ですが計算結果が違っていることが分かると思います。証明は省略しますが、数学公式を使って計算した結果のほうが正しい (正確な) 答えになります。

プログラムでゴリ押し計算した値にズレが生じるのは「情報2」の授業で習ったように 丸め誤差桁落ち誤差 の影響によるものです (情報2の講義ノートの p.58-59 を参照)。公式を使った場合でも、それら誤差は避けられませんが、繰返し構文よって累積しないため影響が小さく抑えられています。

9.2 計算時間を比較

GoogelColab.では %%timeit というマジックコマンドを使ってコードセル単位の実行時間を計測することができます。

%%timeit では、何回もコードセルを実行し、その実行時間の平均標準偏差 (=「情報1」で学習したように「ばらつき」の指標 ) を出力します。そのため、%%timeit を使用する場合には print などの出力はコメントしておきます。また、%reset -f もコメントアウトしておきます。

次のコードを Colab. に貼り付けて実行してみてください。

%%timeit
# %reset -f
a = 16
r = -1/3
n = 50
S = a*(r**n-1)/(r-1)
# print(f'S = {S:.20f}')
%%timeit
# %reset -f
a = 16
r = -1/3
n = 50
S = 0
for i in range(n):
  S += a*r**i
# print(f'S = {S:.20f}')

実行結果の一例を示します。これらの実行結果は、各自に割り当てられた仮想マシンのスペックに依存するので注意してください。

「method-1b.py」の実行結果

208 ns ± 9.64 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

「method-2b.py」の実行結果

9.63 µs ± 3.1 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

これは \(1,000,000\) 回のループを \(7\) 回実行したとき、その \(7\) 回の実行についての 平均実行時間\(208(\mathrm{ns})\) で、その標準偏差\(9.64(\mathrm{ns})\) であった、という意味になります。

10 繰返し構文のネスト

条件分岐構文 (if-elif-else) と同様に、繰返し構文 (for) についてもネスト (入れ子) が構成できます。このようなネストした繰返し構文を「多重ループ (2重ループ)」や「多重繰り返し文 (2重繰返し文)」のようにいいます。

例えば「掛け算の九九の表」は、次のように2重ループで出力できます。

%reset -f
print('-'*27)
for x in range(9):
  for y in range(9):
    p = (x+1) * (y+1)
    print(f'{p:>2d}',end=' ')
  print('') # 1段ごとに改行
print('-'*27)

実行すると次のような出力が得られます。

---------------------------
 1  2  3  4  5  6  7  8  9 
 2  4  6  8 10 12 14 16 18 
 3  6  9 12 15 18 21 24 27 
 4  8 12 16 20 24 28 32 36 
 5 10 15 20 25 30 35 40 45 
 6 12 18 24 30 36 42 48 54 
 7 14 21 28 35 42 49 56 63 
 8 16 24 32 40 48 56 64 72 
 9 18 27 36 45 54 63 72 81 
---------------------------

10.1 応用例1

第04回講義で利用した タートルグラフィックス (Turtle Graphics) を再び使用します。独立したコードセルを作成して、以下を実行してください。これは、接続している仮想マシン (Linux) に「ColabTurtle」というPythonモジュール (ライブラリ) をインストールするコマンドになります。

!pip install ColabTurtle

Colab.の場合、インストールしたライブラリは、ウェブブラウザと仮想マシンと接続が切れると、仮想マシンと共に破棄されます (再度、Colab.ノートブックを開いたときは、再度、上記コマンドを実行する必要があります) 。

pip install とは (ChatGPT先生に聞いてみた)

pip install はPythonのプログラミングツールのコマンドです。これを使うと、特定の機能を持つコードの集まり(ライブラリと呼ばれる)をダウンロードしてPythonプログラムのなかで利用できるようにすることができます。例えば pip install pandas とすると、データ分析に使われる pandas というライブラリをインストールできます。つまり pip install は、Pythonで新しい機能を手軽に追加するための道具として使用されます。

多重ループを利用して、次のようなドットマトリクスを描画してみます。

matrix

次のプログラムをColab.ノートブックに貼り付けて、実行してください。

%reset -f
import ColabTurtle.Turtle as t

# 初期設定
w, h = 400,200
t.initializeTurtle(initial_speed=13,initial_window_size=(w,h))
t.shape('circle');t.hideturtle();t.bgcolor(0,0,0)
t.width(12) # 軌跡幅(ペンの太さ)

# 2重繰り返し(2重ループ)
for x in range(19):
  for y in range(9):
    # 色設定 RGB
    t.color(255-x*10, 255-y*25, 100)
    # 点描画(現在位置から現在位置に移動軌跡を描く)
    t.up();   t.goto(x*20+20,y*20+20)
    t.down(); t.goto(x*20+20,y*20+20)

ColabTurtleの詳細は、こちら を参照してください。

10.2 演習⑥

次のようなドットマトリクスを描画せよ。

matrix

10.3 応用例2

多重ループを使うことで、次ようなサイバーなロゴを描くこともできます。

ERROR-LOGO

上記を描くためのプログラムは以下のようになります。

同心多角形の描画の観点では34行目がポイントとなります (1年の「基礎数学」で学んだ三角関数の実用例です。例えば、34行目を t.goto(cx-r/2,cy+r/2) に書き換えると (実際に試してみて!) 美しい同心多角形にならないことが分かると思います) 。

%reset -f
import math
import ColabTurtle.Turtle as t

n=6  # N角形
r=50 # 一片の長さ
cx,cy = 250,200 # 中心座標

# 初期設定
t.initializeTurtle(initial_speed=13,initial_window_size=(cx*2,cy*2))
t.shape('circle');t.hideturtle();t.bgcolor(0,0,0)

# N角形の外角を計算しておく
deg = 180-360/n
rad = math.radians(deg)

# 色の初期値
red = 255
pen = 10

# 文字
t.color(red,0,0)
t.up()
t.goto(cx, cy+6)
t.write('ERROR',align='center',font=(20,'Arial','bold'))

# 2重ループ
for p in range(10):
  t.up()
  t.width(pen)
  t.color(red,0,0)
  t.goto(cx, cy)
  t.setheading(0) # 向きを0°(3時方向)に設定
  t.goto(cx-r/2,cy+math.tan(rad/2)*r/2)
  t.down();

  # N角形を描画
  for i in range(n):
    t.forward(r)
    t.left(180-deg);

  # 辺の長さと色を更新
  r+=18
  red-=25

10.4 演習⑦

上記のプログラムに手を加えて美しく魅力的な画像を生成せよ。

11 PG1-課題03のヒント

いずれの問題も、まずは 紙上に図を描いて考えること が何よりも大事です。この作業もしないまま「できない」「分からない」と投げ出したり、悲観したりするとのは、非常に愚かです。

また、出題ページの末尾に記載される 補足とヒント にも、必ず目を通してください。

11.1 その点はXY平面の第何象限に存在するのか

負数に対して % 演算子を使用する場合に注意してください。

%reset -f
print(  10%3    ) # ->  1
print(  10%(-3) ) # -> -2
print( -10%3    ) # ->  1
print( -10%(-3) ) # -> -2

11.2 バツ印の描画のための座標計算

バツ印は、1本目の直接の中点で、2本目の直線と交差します。よって 中点を通って1本目の直線と垂直な線 を描くことでバツ印が描けます。

あるベクトルと直交するベクトルの求め方は、数学の教科書やウェブを参照すればすぐに見つけることができます。

もしくは、次のような幾何学的な性質に着目して解くこともできます。緑の実線と破線の長さが同じであること、オレンジの実線と破線の長さが同じであることを利用します

hint1