制御フロー#

in English or the language of your choice.

# 警告メッセージを非表示
import warnings
warnings.filterwarnings("ignore")

制御フロー(control flow)とは,コンピューターがコードを実行する順序を意味する。通常,コードセルの中では「上から下に」実行することになるが,その中でフローが条件分岐したり,ある作業を何百回・何万回も繰り返したりすることになる。ここでは条件分岐と繰り返しに注目しPythonの使い方を説明する。

フローチャート#

本題に入る前に,まずフローチャートについて説明する。フローチャートを使うと,より直観的に制御フローを理解することができるだろう。フローチャートとは,コードの実行やプログラムの処理の流れ(順番)を示す図である。非常に簡単で,見れば直ぐに理解できる便利なツールである。次のコードを考えてみよう。

x = 10      #1
y = x + 5   #2
print(y)    #3
15

このコード・セルを実行すると上から順に次のように実行される。

  • #1の行:10を変数xに割り当てる。

  • #2の行:#1が終わった後に,xの値と5を足し合わせ変数yに割り当てる。

  • #3の行:#2が終わった後に,yの値をprint()関数を使い表示する。

この流れをフローチャートに表してみよう。

Hide code cell source
from schemdraw import flow
import schemdraw
import japanize_matplotlib

schemdraw.config(lw=1, font='IPAexGothic')
with schemdraw.Drawing() as d:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(d.unit/4)
    h = flow.Box(w=2, h=1).label('x = 10')
    flow.Arrow().down(d.unit/4)
    h = flow.Box(w=2, h=1).label('y = x + 5')
    flow.Arrow().down(d.unit/4)
    h = flow.Box(w=2, h=1).label('print(y)')
    flow.Arrow().down(d.unit/4)
    flow.Start(w=2, h=1).label('END')
Matplotlib is building the font cache; this may take a moment.
_images/095195047acbc623e2ae8346838d5c3bc81042082ba945371974163e57a33158.svg

一番最初と最後の楕円は,プログラムの始まり(START)と終わり(END)を表す。長方形はコードの処理,そして,矢印はプログラムの実行の順番(フロー)を意味する。単純な例なので,上から下に一直線の実行フローとなっている。より複雑なコードの場合,矢印が横向きや上向きになったりもし,枠も様々な形を使い実行フローを表現することになる。以下では,枠は次の3種類だけを使い制御フローをシンプルな形で表すことにする。

  • 楕円(端子):プログラムの始まりと終わり

  • 長方形(処理):コードの処理

  • 菱形(判断):条件分岐

また,矢印にTrueFalse等を付け加えて条件分岐を表現することになる。

if#

説明と簡単な例#

if文を使うと,指定した条件によって処理を複数パターンに分けることができる。条件をブール型(真偽)(True又はFalse)で判断しコードを実行することになる。次の構文となる。

if <条件1>:
    <条件1が`True`の場合に実行するコード>
    
elif <条件2>:
    <条件2が`True`の場合に実行するコード>
    
elif <条件3>:
    <条件3が`True`の場合に実行するコード>
    ....
else:
    <全ての条件が`False`の場合に実行するコード>
  • 1行目はifで始まり:で終わる。

  • <条件...がTrueの場合に実行するコード>の行はインデント(4つの半角スペース)されている。

  • elif(else ifの略)の行も:で終わる。

  • elseの行も:で終わる。

  • 条件が1つの場合は,elifがある行を書く必要はない。

  • elseの行は省略可能。その場合は,次のコードと等しい。

    else:
        pass
    

    即ち,「全ての条件がFalseの場合は何も実行しない」という意味になる。

条件が1つの例を考えよう。

x = 10                       #1

if x == 10:                  #2
    print('条件はTrueです')    #3

else:                        #4
    print('条件はFalseです')   #5
条件はTrueです

<コードのの説明>

  • #110xに割り当てる。

  • #2x10と等しいかを判断する。

    • x==10Trueの場合は,#3が実行され,条件はTrueですが表示さる。コード・セルの実行はこれで終了する。

    • x==10Falseの場合は,#3は実行されず,#4に進む。

  • #4からx==10Falseのケースがはじまる。

  • #5が実行され,条件はFalseですが表示さる。コード・セルの実行はこれで終了する。

もちろん,上のコード・セルを実行すると,#3で処理は終了することになる。このif文をフローチャートで表してみよう。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    
    b = flow.Box(w=2, h=1).label('x = 10')
    flow.Arrow().down(sd.unit/4)
    
    d = flow.Decision(w=3, h=1.3, S='False', E='True').label('x = 10 ?').drop('S')
    
    flow.Arrow().right().at(d.E).length(2)
    b = flow.Box(w=4.2, h=1).label("print('条件はTrueです')").drop('S')
    flow.Arrow().down(sd.unit/4)
    flow.Start(w=2, h=1).label('END')
    
    flow.Arrow().down(sd.unit/3).at(d.S)
    b = flow.Box(w=4.2, h=1).label("print('条件はFalseです')")
    flow.Arrow().down(sd.unit/4)
    flow.Start(w=2, h=1).label('END')
_images/868682c05cc532b137530ca06227cf880353bdff93ddb9321998c2f1bae25dd0.svg

菱形枠中のは「判断」を意味するが,上のコードの#2に対応している。x=10Trueなので,菱形枠から右に移動して条件はTrueですが表示されることになる。

次の例は上の例と似ているが少しだけ異なる。

x = 20                        #1

if x == 10:                   #2
    print('条件はTrueです')    #3

else:                         #4
    pass                      #5

<コードの文の説明>

  • 上のif文と異なるのは#5だけである。passは「何もしない」という意味。

20xに割り当てられているので,x==10Falseを返す。従って,#3は飛ばしてelseのブロックに移ることになる。その場合の実行コードはpassなので何も表示されないことになる。

このコードでelse以下を省略してもエラーにはならない(結果も変わらない)。即ち,else以下がない場合は,#3#4が省略されていると考えることができる。

フローチャートで表すと次のようになる。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    
    b = flow.Box(w=2, h=1).label('x = 10')
    flow.Arrow().down(sd.unit/4)
    
    d = flow.Decision(w=3, h=1.3, S='False', E='True').label('x = 10 ?').drop('S')
    flow.Arrow().right().at(d.E).length(1.5)
    flow.Box(w=4, h=1).label("print('条件はTrueです')").drop('S')
    flow.Arrow().down(sd.unit/4)
    flow.Start(w=2, h=1).label('END')
#     flow.Start(w=2, h=1).anchor('W').label('END')
    
    flow.Arrow().down(sd.unit/3).at(d.S)
    flow.Start(w=2, h=1).label('END')
_images/2d6f22d80bf143e403c2b6a3d8d543eaa3f74f31e277c19293710cd875399f9d.svg

Falseと判断されると,直ぐにENDとなっている。

複数条件#

次に,複数の条件がある例を考える。change_in_gdpをGDPの変化として,次の表示がされるif文を作成する。

  1. change_in_gdpの値が正の場合,GDPは増加を表示する。

  2. change_in_gdpの値が負の場合,GDPは減少を表示する。

  3. change_in_gdpの値が0の場合,GDPは不変を表示する。

change_in_gdpの値が200として,フローチャートで表すと次のようになる。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    b = flow.Box(w=4, h=1).label('change_in_gdp = 200')
    flow.Arrow().down(sd.unit/4)
    d = flow.Decision(w=5, h=1.3, S='False', E='True'
                     ).label('change_in_gdp > 0 ?')
    
    flow.Arrow().right().at(d.E).length(1.5)
    b = flow.Box(w=3.7, h=1.).label("print('GDPは増加')").drop('S')
    flow.Arrow().down(sd.unit/5)
    flow.Start(w=2, h=1).label('END')
    
    flow.Arrow().down(sd.unit/2).at(d.S)
    d = flow.Decision(w=5, h=1.3, S='False', E='True'
                 ).label('change_in_gdp < 0 ?')

    flow.Arrow().right().at(d.E).length(1.5)
    b = flow.Box(w=3.7, h=1.).label("print('GDPは減少')").drop('S')
    flow.Arrow().down(sd.unit/5)
    flow.Start(w=2, h=1).label('END')

    flow.Arrow().down(sd.unit/3).at(d.S)
    b = flow.Box(w=3.7, h=1.).label("print('GDPは不変')").drop('S')
    flow.Arrow().down(sd.unit/4)
    flow.Start(w=2, h=1).label('END')
_images/4e4dfd95a04c958edd82f6fade3c1032b03b43a370f7cb21cdadf60ba33e7be6.svg

このフローチャートをコードに落とし込むと次のようになる。

change_in_gdp = 200  #1

if change_in_gdp > 0:
    print('GDPは増加')

elif change_in_gdp < 0:
    print('GDPは減少')

else:
    print('GDPは不変')
GDPは増加

if文の説明>

  • ifが1つ目の条件, elifが2つ目の条件, elseが3つ目の条件を指定している。

  • if, elif, else で始まる行の最後は:となる。

  • print で始まる行には4つの半角スペースのインデントが入る

  • elseの行にchange_in_gdp==0は不要(残りの可能性はchange_in_gdp==0しかないため)

  • elifelse ifの省略形であり,2つ目の条件を定義する。

  • elififelseの間に複数入れることが可能である。

コードを実行すると,#1200change_in_gdp割り当てられているのでGDPは増加が表示されることになる。

このコードを次のように入れ子のif文として書くことも可能である。

change_in_gdp = 200

if change_in_gdp > 0:               #1
    print('GDPは増加')

else:                               #2
    if change_in_gdp < 0:           #3
        print('GDPは減少')

    else:                           #4
        print('GDPは不変')
GDPは増加

if文の説明>

  • #1#2を使って0で条件分岐させている。

    • #1change_in_gdpが正であればGDPは増加しています。が表示される。

    • #2change_in_gdpが非正の場合は次の条件分岐に進む。

  • #3#4を使って,0で条件分岐させている。

    • #3change_in_gdpが負であればGDPは減少しています。が表示される。

    • #4change_in_gdp0であればGDPは変化していません。が表示される。

このコードを見ると,複数条件の場合に使うelifelse ifの略だと気づくはずだ。またコードを読んで感じることは,入れ子にすると可読性が落ちることだ。複数条件の場合は,elifを使うようにしよう!

関数化#

if文は関数の中で使うことも可能である。GDPに関する例を使って考えてみよう。

def gdp_change(change_in_gdp):
    
    if change_in_gdp > 0:
        print('GDPは増加')

    elif change_in_gdp < 0:
        print('GDPは減少')

    else:
        print('GDPは不変')

<関数の説明>

  • 引数がchange_in_gdp

  • returnがないので返り値はなく,単にprint()関数で表示をするだけとなる。

  • ifelifelseの行は4つの半角スペースのインデントが入っている。

  • printの行は8つの半角スペースのインデントが入っている。

gdp_change(200)
GDPは増加
gdp_change(-200)
GDPは減少
gdp_change(0)
GDPは不変

直接真偽値を使う#

if文を実行する上で,条件が真偽値True又はFalseを返すかで制御フローが分岐することが分かったと思う。ここで真偽値が重要な役割を果たしているが,bool()関数を使って真偽値をできることを思い出そう。空の文字列''0などは「空」・「無」なのでFalseとなるが,それら以外はTrueとなる。次の例はこの特性を使った例となる。

def truth_value(x):

    if x:   # この行に着目
        print('真偽値はTrueです。')

    else:
        print('真偽値はFalseです。')

このコードで着目してほしいのはif x:の行である。引数x自体が条件文になっている。もし引数に文字列abcが使われると,if 'abc':となり,エラーが発生するのではと思うかも知れないが,そうならない。実はif文の場合,下の左と右のコードは同じなのである。

if x: → if bool(x):

この解釈はどのif文でも成立するので覚えておこう。

truth_value('経済学')
真偽値はTrueです。
truth_value('0')
真偽値はTrueです。
truth_value(0)
真偽値はFalseです。

真偽値を使ったもう1つの例を紹介しよう。

def my_wallet(money_in_my_wallet):
    
    if money_in_my_wallet:   #1
        print('なに買おうかな😄')
    
    else:
        print('一文なしだよ😅')

実行してみよう。

my_wallet(0)
一文なしだよ😅

#1if money_in_my_wallet:について>

  • if money_in_my_wallet != 0と同じである。だがif money_in_my_wallet:の方がより簡単でPython的(Pythonic)だと言える。

  • if money_in_my_wallet:は「もしお財布にお金があれば」と読めるため,より直感的でもある。

数値を返す例#

今までの例ではprint()関数を使ったが,もちろん数値を返すif文を書くことも可能であり,むしろそれが一般的だろう。次の関数を考えてみよう。

\[ y = \left|x\right| \]

まず,フローチャートを考えてみる。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    
    d = flow.Decision(w=3.5, h=1.2, S='False', E='True'
                     ).label(r'$x=0$ ?')
    
    flow.Arrow().right().at(d.E).length(1.5)
    flow.Box(w=2., h=0.9).label(r'$0$')
    flow.Arrow().right(sd.unit/4)
    flow.Start(w=2, h=1).label('END').anchor('W')
    
    flow.Arrow().down().at(d.S).length(1.)
    d = flow.Decision(w=3.5, h=1.2, S='False', E='True'
                     ).label(r'$x>0$ ?')
    
    flow.Arrow().right().at(d.E).length(1.5)
    flow.Box(w=2., h=0.9).label(r'$x$')
    flow.Arrow().right(sd.unit/4)
    flow.Start(w=2, h=1).label('END').anchor('W')
    
    flow.Arrow().down(sd.unit/3).at(d.S)
    flow.Box(w=2, h=0.9).label(r'$-x$').drop('S')
    flow.Arrow().down(sd.unit/4)
    flow.Start(w=2, h=1).label('END')
_images/138811d8d485098b37bc8d6700a154ac9a25bd853b00e3a586b190728381c809.svg

ここでは,まず \(x=0\)TrueFalseかを判断し,次に \(x>0\)TrueFalseかを判定している。しかし,図から分かるように,この2つの判断は,非常に似ており,次のように1つにまとめることができる。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    
    d = flow.Decision(w=3.5, h=1.2, S='False', E='True'
                     ).label(r'$x\geq 0$ ?').drop('S')
    
    flow.Arrow().right().at(d.E).length(1.5)
    b = flow.Box(w=2., h=0.9).label(r'$x$').drop('S')
    flow.Arrow().down(sd.unit/5)
    flow.Start(w=2, h=1).label('END')
    
    flow.Arrow().down(sd.unit/3).at(d.S)
    b = flow.Box(w=2, h=0.9).label(r'$-x$').drop('S')
    flow.Arrow().down(sd.unit/4)
    flow.Start(w=2, h=1).label('END')
_images/66fe88fddafea3bd0f5f5cca50ae03326fd783be74b168609fe68b24c67612d3.svg

このフローチャートに従って,if文を使いコードを書いてみよう。

x = -10       # 0

if x >= 0:    # 1
    y = x
    
else:         # 2
    y = -x
        
y             # 3
10

if文の説明>

  • (#1) xが非負であれば,xyに割り当てられる。

  • (#2) xが負であれば,-xyに割り当てられる。

#0-10xに割り当てられているので,#3yには10が割り当てられている。従って,yを実行すると10が表示されることになる。

次に,このif文を関数にまとめてみよう。

def f(x):
    
    if x >= 0:
        y = x
        return y

    else:
        y = -x
        return y

上のif文との主な違い:

  • xを引数に使う。

  • 関数の戻り値を設定するためにreturn yが追加されている。

更に,短くするには次のように書くこともできる。

def f(x):
    
    if x >= 0:
        return x

    else:
        return -x

違いは変数yを使わずに,直接結果を返している。

f(10), f(-10), f(0)
(10, 10, 0)

経済学の例として一般的な生産関数を考えよう。

\[ Y=F(K,L) \]

要素間の代替の弾力性は次のように定義される。

\[ \sigma = \dfrac{d\log(L/K)}{d\log\left(\frac{dF}{dK}/\frac{dF}{dL}\right)} \]

\(\sigma\)が一定な生産関数はCES生産関数(Constant Elasticity of Substitution)と呼ばれ,次の式で与えられる。

\[ Y = A\left[a(bK)^\rho+(1-a)(cL)^\rho\right]^{\frac{1}{\rho}} \]

ここで

  • \(\sigma=\dfrac{1}{1-\rho}\)

  • \(\rho\leq 1\):要素の代替の程度を決定する。

  • \(0<a<1\):要素の貢献度のシェアを決定する。

  • \(b>0,c>\):要素の単位に依存する。

  • \(A>0\):生産の単位に依存する(生産性)。

また,\(\rho\)の値によって次のような生産関数となる。

\[\begin{split} Y = \begin{cases} &A\left[a bK+(1-a)cL\right],\quad\rho=1\quad \text{(完全代替型)}\\ &AK^a L^{1-a},\quad\rho=0\quad\text{(コブ・ダグラス型)}\\ &A\cdot\text{min}\left\{bK, cL\right\},\quad\rho=-\infty\quad\text{(完全補完型またはレオンティエフ型)} \end{cases} \end{split}\]

次のコードでは,\(\rho=-\infty\)以外のケースを関数で表している。

def ces_production(k, l, rho=0, A=1, a=0.3, b=1, c=1):
    
    if rho > 1:
        print('rhoには1以下の数字を入力してください。')

    elif rho == 1:
        return A*( a*b*k + (1-a)*c*l )
    
    elif rho == 0:
        return A*k**a * l**(1-a)
    
    else:
        return A*( a*(b*k)**rho + (1-a)*(c*l)**rho )**(1/rho)
ces_production(10, 3, rho=-1)
3.79746835443038

forループ#

説明#

ループとは同じコードを複数回続けて実行できるように書かれたコードを指す。ループには2つのタイプがあるが,forループは指定した回数だけ処理を繰り返す計算手続きである。次のような書き方となる。

for <イタラブルの要素を割り当てる変数> in <イタラブル>:
    <毎回実行したいコード>
  • 1行目

    • <イタラブル>(iterable)とはリストやタプルのように要素を1つずつ返すことができる反復可能なデータ型(オブジェクト)を指す。文字列や後に説明するNumpyarrayも含まれる。

    • <イタラブルの要素を割り当てる変数>とはループを1回実行する毎に<イタラブル>の要素の値を割り当てる変数のこと。よくijなどが使われ,再割り当てされても問題がない変数名を使おう。

    • forで始まり:で終わり,<イタラブル>の前にinが入る。

  • 2行目以降

    • 慣例では4つの半角スペースのインデント後に毎回実行したいコードを書く。

Note

forループでは無限ループは発生しない。リストなどのイタラブルの最後の要素が使われると,自動でループが終了する仕様となっている。

以下では例を使って説明しよう。

print()を使う例#

次のリストにはGDPの構成要素が並んでいる。

gdp_components = ['消費', '投資', '政府支出', '純輸出']

このリストにある文字列を表示したいとしよう。

for i in gdp_components:
    
    print(i)
消費
投資
政府支出
純輸出

<コードの説明>

  • 1回目のループ

    • 1行目でgdp_componentsの0番目の要素消費iに割り当てる。

    • 2行目でprint()関数を使い変数iの値を表示する。

  • 2回目のループ

    • 1行目でgdp_componentsの1番目の要素投資iに割り当てる。

    • 2行目でprint()関数を使い変数iの値を表示する。

  • 3回目のループ

    • 1行目でgdp_componentsの2番目の要素政府支出iに割り当てる。

    • 2行目でprint()関数を使い変数iの値を表示する。

  • 4回目のループ

    • 1行目でgdp_componentsの最後の要素純輸出iに割り当てる。

    • 2行目でprint()関数を使い変数iの値を表示する。

この例ではgdp_componentsの要素の数だけループが行われる。

言葉での説明をフローチャートで表すと次のようになる。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    
    b1 = flow.Box(w=4.1, h=1).label('gdp_componentsの\n'r'要素を $i$ に割り当てる')
    flow.Arrow().down(sd.unit/4)
    
    d = flow.Decision(w=4.2, h=1.7, S='False', E='True'
                     ).label('最後の要素を\n使い切ったか?').drop('E')
    
    flow.Arrow().right(sd.unit/2)
    flow.Start(w=2, h=1).label('END').anchor('W')
    
    flow.Arrow().down(sd.unit/2.4).at(d.S)
    b2 = flow.Box(w=3.2, h=1).label('毎回実行したい\nコードを実行').drop('W')
    
    flow.Wire('c', k=-1.5, arrow='->').linewidth(1).to(b1.W)
_images/072555de28d880b558769c3b792d33ba6658928c21d5ae447c748b1191b4c471.svg

この図から,forループの裏では菱形の判断,即ち,if文が動いていることが分かる。gdp_componentsの最後の要素のループ処理が終わったかどうかを判断している。

Hint

上のフローチャートにある菱形はif文に対応すると説明したが,その点について補足説明をする。もう一度gdp_componentsを使ったforループを考えてみよう。次の2つの説明の内,一つは間違った説明であり,もう一つは正しい説明となる。

【間違った説明】
最初のループで消費を変数iに割り当て,それがgdp_componentsの最後の要素である純輸出と等しいかを比べる。それに続くループでも,同様の比較をおこなう。変数i純輸出が割り当てられると,i純輸出は同じなので,print(i)を実行後にforループを終了する。

【正しい説明】
最初のループで消費を変数iに割り当て,print(i)を実行する。続くループでも同様の処理をおこなう。純輸出のループ処理が終わった後,更に次の要素を変数iに割り当てようとすると,「次の要素」は無いためStopIterationという「例外」(エラーに類似するもの)が発生し,その時点でループは終了する。即ち,フローチャートの菱形では,最後の要素を使い切った後に起こるStopIterationの「例外」が発生したかどうかを判断している。この特徴により,以下で説明するwhileループよりも処理速度が速くなる。

.append()を使う例#

リストにはメソッド.append()が実装されており,これを使うとリストに値を追加することができる。.append()forループを使い,空のリストに値を追加し新たなリストを作成する方法を紹介する。まず元になるリストを作成しよう。

var_lst = [1,2,3,4,5]

以下では,var_lstのそれぞれの要素の10倍からなるリストを新たに作成する。

my_lst = []              # 1

for i in var_lst:        # 2
    
    x = 10 * i           # 3
    
    my_lst.append(x)     # 4

my_lst                   # 5
[10, 20, 30, 40, 50]

<コードの説明>

  1. 空のリストの作成(my_lstに10倍にした数字を格納する)

  2. ここからforループの始まり。iはリスト[1,2,3,4,5]の要素を割り当てる変数。

    • 1回目のループではi1を割り当てる。

    • 2回目のループではi2を割り当てる。

    • 3回目のループではi3を割り当てる。

    • 4回目のループではi4を割り当てる。

    • 5回目のループではi5を割り当てる。

  3. 10*iを計算しxに割り当てる。

  4. .append()を使いxの値をmy_lstに追加する。

  5. my_lstを表示する。

このコードのフローチャートを書いてみよう!

消費関数#

forループを使い,所得によって消費がどのように変化するかを考えてみよう。まずyを所得として消費関数を次のように仮定する。

消費 = 100 + 0.7 * y

ここで

  • 100:自発的消費(autonomous consumption)

    • 可処分所得がゼロであっても発生する消費支出

  • 0.7:限界消費性向(marginal propensity to consume)

    • 可処分所得が1単位増加した場合に,どれだけ消費が増えるかを示す。例えば,月給が10,000円増えたとすると,その場合,7000円を消費に支出することを意味する。

所得は次のリストで与えられるとする。

income_lst = [1000, 1100, 1500, 2000, 2300, 3000] 
c_lst = []               # 1

for y in income_lst:     # 2
    
    con = 100 + 0.7 * y  # 3
    
    c_lst.append(con)    # 4

c_lst                    # 5
[800.0, 870.0, 1150.0, 1500.0, 1710.0, 2200.0]

<コードの説明>

  • #1:空のリストの作成(消費の値を格納する)

  • #2: ここからforループの始まり。yはリストincome_lstの要素を割り当てる変数。

    • 1回目のループではy1000を割り当てる。

    • 2回目のループではy1100を割り当てる。

    • 3回目のループではy1500を割り当てる。

    • 4回目のループではy2000を割り当てる。

    • 5回目のループではy2300を割り当てる。

    • 6回目のループではy3000を割り当てる。

  • #3:消費を計算しconに割り当てる。

  • #4.append()を使いconの値をc_lstに追加する。

  • #5c_lstを表示する。

forループの2つの書き方(動学分析の基礎)#

上で説明した3つのforループは次の特徴を持っている。

  • print()を使った例

    • イタラブルの要素を使い,それを表示する。

  • .append()を使った例

    • イタラブルの要素を使い,それを予め準備したリストに追加する。

  • 消費関数の例

    • イタラブルの要素を使い消費を計算し,予め準備したリストに追加する。

3つに共通することは,n回目に計算した値はn-1回目に計算した値から独立だということである。例えば,消費関数を考えてみよう。3回目のループで計算した消費量はその回の所得に依存するが,2回目ループの消費量や所得には依存しない。print().append()の例も同じである。

一方,マクロ経済学では,今期の値は前期の値に依存する場合が頻繁に出てくる。典型的な例が資本ストックである。t期末の資本ストックはt期の投資とt-1期末の資本ストックによって決定される。ここでは,このように動的に変化する変数を捉えるためのforループの書き方を紹介するのが目的である。

投資と資本ストックはフローとストックの関係にあるが,お風呂の浴槽にバケツで水を足していくことをイメージすれば簡単に理解できるだろう。

  • 浴槽に溜った水 → 資本ストック(ストックの概念)

  • バケツの水 → 投資(フローの概念)

初期の資本ストック(浴槽の水)0であり,毎期ごと10単位の投資(10リットルのバケツで水を追加)するとしよう。問題を簡単にするために5期間(水を追加するのは5回)とする。資本ストック(浴槽の水量)の推移は次のリストで表すことができる。forループを使って,このリストを作成することが目的である。

Hide code cell source
[10*i for i in range(5+1)]
[0, 10, 20, 30, 40, 50]

次のコードが一つ目の方法である。変数kを資本ストック,#310を投資,tを時間と考えればより分かり易いだろう。k_lstに資本ストックの時系列の値が格納されることになる。

k_lst = [0]               #1

for t in range(1,6):      #2
    
    k = k_lst[t-1] + 10   #3
    
    k_lst.append(k)       #4
    
k_lst                     #5
[0, 10, 20, 30, 40, 50]

<コードの説明>

  • #1k_lstに初期値の0が置いてある。以下ではk_lstに値を追加して目的のリストを作成する。

  • #2:ループの開始。

    • イタラブルとしてrange(1,6)を使っている。list(range(1,6))と置いても良いが,range(1,6)だけで役目は果たす。

    • range(1,6)1から5までの整数を準備し,5回ループが実行されることになる。

    • 1から5までの整数を順次tに割り当てることになる。

  • 1回目のループ

    • #3t=1となる。k_lst0番目の要素である0を抽出し,それに10をを足すことにより右辺は10となる。それをkに割り当てる。

    • #4kの値10k_lstに追加する。10k_lstの1番目の要素となる。

  • 2回目のループ

    • #3t=2となる。k_lst1番目の要素である10を抽出し,それに10をを足すことにより右辺は20となる。それをkに割り当てる。

    • #4kの値20k_lstに追加する。20k_lstの2番目の要素となる。

  • 3回目のループ

    • #3t=3となる。k_lst2番目の要素である20を抽出し,それに10をを足すことにより右辺は30となる。それをkに割り当てる。

    • #4kの値30k_lstに追加する。30k_lstの3番目の要素となる。

  • 同様のループ計算が合計で5回繰り返される。

  • #5k_lstを表示する。

この例では,ループ用の変数t#3で使われており,比較的に分かりやすいと思う人も多いだろう。一方で,tに対応する変数(即ち,k_lst[t-1])が何を指しているのかを考える必要があり少し煩雑と感じる人もいるだろう。

次のコードでも同じ結果となるが,tに対応する変数の値を考える必要はない。

k = 0                   #1

k_lst = [k]             #2

for t in range(5):      #3
    
    k = k + 10          #4
    
    k_lst.append(k)     #5
    
k_lst                   #6
[0, 10, 20, 30, 40, 50]

<コードの説明>

  • #1:最初のkの値を設定する。またkはループのアップデート用の変数として使われる。

  • #2kの初期値0からなるリストの作成する。以下ではk_lstに値を追加して目的のリストを作成する。

  • #3:ループの開始。

    • イタラブルとしてrange(5)を使っている。list(range(5))と置いても良いが,range(5)で十分である。

    • range(5)0から4までの整数を準備し,5回ループが実行されることになる。

    • 0から4までの整数を順次tに割り当てることになる。

    • 4〜5行目にtは使われていない。従って,tの役割は0から4までの整数の「捨て場」のようなものである。そのような場合,tの代わりに_を使うこともある。

  • 1回目のループ

    • #4:右辺のkの値は#1で割り当てた0である。それに10を足し右辺は10となり,それを左辺のkに割り当てる。この時点で,1行目のk=0は「無効」となり,k=10として更新される。

    • #5kの値10k_lstに追加する。

  • 2回目のループ

    • #4:右辺のkの値は10である。それに10を足し右辺は20となり,それを左辺のkに割り当てる。この時点でk=10は「無効」となり,k=20として更新される。

    • #5kの値20k_lstに追加する。

  • 3回目のループ

    • #4:右辺のkの値は20である。それに10を足し右辺は30となり,それを左辺のkに割り当てる。この時点でk=20は「無効」となり,k=30として更新される。

    • #5kの値30k_lstに追加する。

  • 同様のループ計算が合計で5回繰り返される。

  • #6k_lstを表示する。

1行目でk=0を設定していたが,ループ終了後の値を確認してみよう。

k
50

k_lstの最後の要素と同じであり,forループ内で更新用の変数として使われたのがわかる。この点に関する注意点としてここの箇所を復習を勧める。

2つの書き方を紹介した。基本的にどちらを使っても良い。上でも書いたが,最初の方法はtに対応する変数の値を追う必要があるので,それを分かりやすいと感じる人もいるだろうし,面倒と感じる人もいるかも知れない。一方,2つ目の方法はtを確認する必要がない。もちろん,初期値の変数k=0forループの外に作る必要があるが,これは次の例で説明するように,関数の中で使う場合は問題にはならない。このサイトでは主に2つ目の手法を多用することになる。

次に上のコードを関数化してみよう。 初期時点の資本ストックk0と期間periodを引数とする。

def capital_accumulation(k0, period):

    k = k0   # アップデート用の変数の生成

    k_lst = [k]

    for t in range(period):
        
        k = k + 10
        
        k_lst.append(k)

    return k_lst

capital_accumulation(0, 5)
[0, 10, 20, 30, 40, 50]

このコードではk=k0でアップデート用の変数の生成している。実は,このコードをもう少し縮めることができる。

def capital_accumulation_shorter(k, period):

    k_list = [k]

    for t in range(period):
        k = k + 10
        k_list.append(k)

    return k_list

capital_accumulation_shorter(0, 5)
[0, 10, 20, 30, 40, 50]

capital_accumulation関数とcapital_accumulation_shorter関数には2箇所違いがある。

  • 後者では引数にkが使われている。

  • 後者ではk=k0の行がない。

後者の引数kが,初期値の設定とアップデート用変数の作成の2つの役割を同時に果たしている。capital_accumulation(0,5)を実行するとk=0が実行され,0が初期値に設定される。それと同時に,変数kがメモリー上に生成され,それがアップデート用の変数として使われることになる。

将来価値の時系列#

元本x0万円を投資すると実質年率r%の利息を得る金融商品を考えよう。t年後の将来価値(期首の値)をリストで示す関数を作成する。

def calculate_futre_value(x0, r, t):  #1
    
    x = x0                            #2
    
    value_lst = [x]                   #3
    
    for _ in range(t):                #4
        
        x = x*(1+r)                   #5
        
        value_lst.append(x)           #6
    
    return value_lst                  #7

<コードの説明>

  • #1:引数

    • x0は元本,r実質利子率,t投資期間

  • #2xforループでのアップデート用の変数として使われる。初期値として元本x0を設定する。

  • #3value_lstに将来価値を追加して目的のリストを作成する。初期値の値である元本xがリストの0番目の要素として入っている。

  • #4range(t)を使って0からt-1までの整数を準備する。_0からt-1までの値を割り当てるだけで他の役割はない。

  • #5:右辺のxは今期の価値であり,来季の価値はx*(1+r)になる。それを左辺のxに割り当てる。その時点でxがアップデートされる。

  • #6xの値をvalue_lstに追加する。

  • #7:戻り値としてvalue_lstを設定する。

次の値を使って将来価値を計算してみよう。

  • 元本:x0=100

  • 実質利子率:r=0.02

  • 期間:t=5

values = calculate_futre_value(100, 0.02, 5)
values
[100, 102.0, 104.04, 106.1208, 108.243216, 110.40808032000001]

このリストには初期値の1001年後から5年後までの値が含まれている。

forループのお友達1:enumerate()関数#

将来価値の時系列の例を考えよう。結果はリストにまとめられているため,他に利用する場合は便利である。一方で,結果を見易く表示したい場合がある。その場合に便利なのがenumerate()関数である。

enumerate()ついて説明するために次のリストを考えよう。

['A','B']

0番目の要素はA1番目の要素はBである。ここで01が要素インデックスである。emunerate()は次の2つをペアにしたイタラブルを返す。

  • リストの要素インデックス

  • 要素

このことは確認するためにlist()関数を使って実行してみよう。

z = list( enumerate(['A','B']) )
z
[(0, 'A'), (1, 'B')]

0番目の要素はタプル(0,'A')であり,1番目の要素はタプル(1,'B')となっている。最初の要素を表示してみよう。

z[0]
(0, 'A')

要素Aのインデックス番号と要素Aの組み合わせのタプルである。タプルのアンパッキングを使い,2つの変数に同時割り当てすることができる。

i, v = z[0]

print(i)
print(v)
0
A

この特徴を利用して,将来価値の時系列の例の結果のリストであるvaluesを見やすく表示する。

for i, v in enumerate(values):   #1
    
    print(f'{i}期:{v:.1f}万円')   #2
0期:100.0万円
1期:102.0万円
2期:104.0万円
3期:106.1万円
4期:108.2万円
5期:110.4万円

<コードの説明>

  • 1回目のループ

    • #1enumerate(values)から(0,100.0)が抽出され,それぞれがivに割り当てられる。

    • #2print()関数を使いivを表示する。

      • f-stringを使っている。

      • {v:.1f}:.1fは小数点第一位までの表示を指定している。

  • 2回目のループ

    • #1enumerate(values)から(1,102.0)が抽出され,それぞれがivに割り当てられる。

    • #2print()関数を使いivを表示する。

  • 同様のループが合計6回おこなわれる。

forループのお友達2:zip()関数#

zip()forループを拡張する上で重宝する関数であり,知っていると得する「裏技」である。消費関数の例を考えよう。そのコードでは,所得yのみが変化しているが,限界消費性向0.7も変化するとしてみよう。このような場合に有用なのがzip()関数である。

より具体的な例を考えるために,所得はincome_lstに従って変化し,限界消費性向は次のリストによって与えられるとしよう。

mpc_lst = [0.7, 0.71, 0.72, 0.73, 0.74, 0.75]

(変数名のmpcは限界消費性向の英訳である Marginal Propensity to Consume から来ている。)

zip()関数の引数にincome_lstmpc_lstを指定して実行してみる。下のコードでは内容が表示されるようにlist()関数を使っている。

z = list( zip(income_lst, mpc_lst) )
z
[(1000, 0.7),
 (1100, 0.71),
 (1500, 0.72),
 (2000, 0.73),
 (2300, 0.74),
 (3000, 0.75)]

n=0,1,2,3,4とするとzは次の構成となっている。

  • zn番目の要素は,income_lstmpc_lstn番目の要素からなるタプル

0番目の要素を抽出してみる。

z[0];

0番目の要素が所得であり,1番目の要素は限界消費性向である。タプルのアンパッキングを使い,2つの変数に同時割り当てすることができる。

y, mpc = z[0]

print(y)
print(mpc)
1000
0.7

この特徴を利用して,消費の変化をforループで計算することができる。

c_lst = []                                # 1

for y, mpc in zip(income_lst, mpc_lst):   # 2
    
    con = 100 + mpc * y                   # 3
    
    c_lst.append(con)                     # 4

c_lst                                     # 5
[800.0, 881.0, 1180.0, 1560.0, 1802.0, 2350.0]

<コードの説明>

  • #1:空のリストの作成(消費の値を格納する)

  • #2: ここからforループの始まり。yはリストincome_lstの要素を割り当てる変数であり,mpcはリストmpc_lstの要素を割り当てる変数となる。。

    • 1回目のループではy=1000mpc=0.7を使い右辺を計算し,結果を左辺のconに割り当てる。

    • 2回目のループではy=1100mpc=0.71を使い右辺を計算し,結果を左辺のconに割り当てる。

    • 3回目のループではy=1500mpc=0.72を使い右辺を計算し,結果を左辺のconに割り当てる。

    • 4回目のループではy=2000mpc=0.73を使い右辺を計算し,結果を左辺のconに割り当てる。

    • 5回目のループではy=2300mpc=0.74を使い右辺を計算し,結果を左辺のconに割り当てる。

    • 6回目のループではy=3000mpc=0.75を使い右辺を計算し,結果を左辺のconに割り当てる。

  • #3:消費を計算しconに割り当てる。

  • #4.append()を使いconの値をc_lstに追加する。

  • #5c_lstを表示する。

mcpが一定な場合と比べて,消費が増加していることが確認できる。

zip()関数の引数には,任意の数のイタラブルが使える。例えば,自発的消費(上の例では100)が次のリストに従って変化する場合の消費の計算も簡単におこなうことができる。

auto_con = [100, 95, 90, 85, 80, 75]

試してみよう!

内包表記#

内包表記とはforループの考えを使い、リストや辞書などを簡単に1行で生成する方法である。リストの場合、次のような書き方となる。

[<実行したい内容> for <要素を割り当てる変数> in <イタラブル>]
  • for <要素を割り当てる変数> in <イタラブル>の箇所はforループの1行目と同じとなる。

  • <イタラブル>はリストやタプルなどを指す。

  • <要素を割り当てる変数>にはiなどを使う。

  • :は入らない。

  • <実行したい内容>の箇所でループで実行したいコードを書く。

<イタラブル>の例としてrange(5)を考えよう。

lst = [i**2 for i in range(5)]
lst
[0, 1, 4, 9, 16]

forループと内包表記を並べると2つの関係がより分かりやすくなるだろう。

lst = []
for i in range(5):
    lst.append(i**2)

[i**2
for i in range(5)]

次の例としてlstの要素を全て文字列に変換してみよう。

[str(i) for i in lst]
['0', '1', '4', '9', '16']

上のコードのstr()は組込み関数だが,defを使って作成した自前の関数を使うことも可能である。試してみよう。

また内包表記にもif文を追加することも可能である。例えば,1から10までの整数で5以下であれば2乗し,それ以外は0にするとしよう。

[x**2 if x<=5 else 0 for x in range(1,10+1)]
[1, 4, 9, 16, 25, 0, 0, 0, 0, 0]

しかし複雑になると可読性が落ちるので注意しよう。

Warning

上の例で,x<=5の場合は2乗し,他はリストに含めないとしよう。その場合,if x<=5は最後に来ることになる。

[x**2 for x in range(1,10+1) if x<=5]

また次の2つの場合はエラーとなるので注意しよう。

[x**2 if x<=5 for x in range(1,10+1)]
[x**2 for x in range(1,10+1) if x<=5 else 0]

Whileループ#

説明と簡単な例#

ループの2つの目タイプはwhileループと呼ばれ,名前からその機能を何となく想像できるかも知れない。与えられた条件がTrueはループ計算を繰り返し続け,Falseになると実行が止まるループとなる。主に,ループの回数が決まっていない場合に使われる。次の書き方となる。

while <条件>:        # 1
    <毎回実行する内容>  # 2

実行される順番は次の様になる。

  1. #1で条件の真偽値を確認する。

    • Falseであればループ終了。

    • Trueであれば#2に進む。

  2. #2のコードの実行を終了すると#1に戻る。

  3. #1で条件の真偽値を確認する。

    • Falseであればループ終了。

    • Trueであれば#2に進む。

  4. #2のコードの実行を終了すると#1に戻る。

#1で条件がFalseになる迄この実行フローが続くことになる。

ここから分かることは,#1<条件>がいずれFalseになるようにコードを書かなければ無限ループに陥ることになる。

Hint

Jupyter Notebookで無限ループに入ってしまった場合は,次の手順で実行を中止することができる。

メニューの「カーネル」をクリック → 「中断」をクリック

もちろん無限ループこそが必要な場合(ウェブサイトで何かを入力する場合など)もあるが,ここでは有限ループを想定してコードを考えることにする。最も基本となるのが次の例である。

counter = 0            #1

while counter < 3:     #2
    
    print(counter)     #3
    
    counter += 1       #4
0
1
2

<コードの説明>

  • #1

    • counterは数字を数えるカウンターの役割を果たす。初期値は0。このカウンター変数の目的は#2で条件がFalseになる状況を作ることである。

  • #2:条件を設定し,ループを続けるか終わらせるかを判断する。

  • #3#4:条件がTrueの場合に実行するコード

    • #4:カウンター変数の値を変更する。この目的は#2で条件がFalseに状況を作ることである。

  • 1回目のループ

    • #2counterの値は0なのでcounter<3Trueを返す。条件が満たされたので#3に進む。

    • #3print()関数を使い変数counterの値を表示する。

    • #4counter1増加する。この時点でcounter=1にアップデートされる。

  • 2回目のループ

    • #2counterの値は1なのでcounter<3Trueを返す。条件が満たされたので#3に進む。

    • #3print()関数を使い変数counterの値を表示する。

    • #4counter1増加し,この時点でcounter=2にアップデートされる。

  • 3回目のループ

    • #2counterの値は2なのでcounter<3Trueを返す。条件が満たされたので#3に進む。

    • #3print()関数を使い変数counterの値を表示する。

    • #4counter1増加し,この時点でcounter=3にアップデートされる。

  • 4回目のループ

    • #2counterの値は3なのでcounter<3Falseを返す。条件が満たされないのでループは中断される

この例で分かることは,4行目のcounter+=1がなければcounterの値は0のままなため無限ループが発生することになる。

フローチャートで表すと次のようになる。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    flow.Box(w=2.2, h=1).label(r'counter = 0').drop('S')
    flow.Arrow().down(sd.unit/4)
    
    d = flow.Decision(w=3.5, h=1.5, S='True', E='False'
                     ).label('counter < 3 ?').drop('E')

    flow.Arrow().right(sd.unit/2).at(d.E)
    flow.Start(w=2, h=1).label('END').anchor('W')
    
    flow.Arrow().down(sd.unit/3).at(d.S)
    flow.Box(w=3.3, h=1).label('print(counter)')
    flow.Arrow().down(sd.unit/3)
    b = flow.Box(w=3., h=1).label('counter += 1')

    flow.Wire('c', k=-1.5, arrow='->').linewidth(1).at(b.W).to(d.W)
_images/3739fd30ab2ca0cddca83f2d6f921d72adb446a31e8e37c725918fcb5b503c90.svg

この図から,最後のcounter+=1がなければ無限ループに陥ってしまうことが分かると思う。また,forループ同様,菱形でif文が裏で働いていることが分かる。一方で,if文の判断で次の点で異なる。

  • whileループ:条件がTrueの場合にループ処理は継続し,Falseの場合に終了する。

  • forループ:条件がFalseの場合にループ処理は継続し,Trueの場合に終了する。

whileループはif文に基づいていることが分かった訳だが,このことから,if文で使ったelsewhileループでも使えたとしても不思議ではないだろう。実際,使うことができる。上の例にelseを使ってみよう。

count = 0

while count < 3:  #1
    
    print(count)
    
    count += 1
    
else:             #2
    print('無事終了(^^)')
0
1
2
無事終了(^^)

4回目のループの初めにcount<3Falseを返したので,elseに進みメッセージを表示している。このコードを見ると,if文と構造が非常に似ていることが分かる。

  • #1:条件の真偽値を確認する。Trueであれば,続くコードを実行する。

  • #2:条件がFalseの場合に実行する。

フローチャートを使うと,次のように表すことができる。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    flow.Box(w=2.2, h=1).label(r'counter = 0').drop('S')
    flow.Arrow().down(sd.unit/4)
    
    d = flow.Decision(w=3.5, h=1.5, S='True', E='False'
                     ).label('counter < 3 ?').drop('E')

    flow.Arrow().right(sd.unit/2).at(d.E)
    flow.Box(w=3.6, h=1).label("print('無事終了(^^)')").drop('S')
    flow.Arrow().down(sd.unit/3)
    flow.Start(w=2, h=1).label('END')
    
    flow.Arrow().down(sd.unit/3).at(d.S)
    flow.Box(w=3.3, h=1).label('print(counter)')
    flow.Arrow().down(sd.unit/3)
    b = flow.Box(w=3., h=1).label('counter += 1')
    
    flow.Wire('c', k=-1.5, arrow='->').linewidth(1).at(b.W).to(d.W)
_images/f80eb28ffe517a8715ba001246e05fee7973d9fbb70f412c73d05b18f41258c4.svg

上のフローチャートと比べると,菱形枠とENDの間にprint('無事終了(^^)')が追加されている。

近似を考える#

whileループとforループをどう使い分けすれば良いだろうか。扱うデータ構造と目的が判断基準となるだろう。リストやタプルのようなイタラブルが用意できるのであれば,forループの実行速度が断然速い。この場合はループの回数が事前に分かっているケースとなるが,分からない場合もあり得る。そういう場合はwhileループを使うべきである。このセクションでは,その例を2つ取り上げる。

壁からの距離#

壁から1メートル離れて立ち,毎回壁までの半分の距離を進むとしよう。壁までの距離を変数distanceで表すと,初期値は1となる。またループをストップする距離を0.001(1mm)とする。whileループを書いて距離の推移を表示してみよう。

distance = 1                   #1

while distance > 0.001:        #2
    
    print(distance)            #3
    
    distance -= distance/2     #4

else:
    print(f'残りの距離は{distance*1000}mmです。')  #5
1
0.5
0.25
0.125
0.0625
0.03125
0.015625
0.0078125
0.00390625
0.001953125
残りの距離は0.9765625mmです。

<コードの説明>

  • 1行目

    • distanceはカウンターの役割を果たす。初期値は1

  • 2行目

    • distance>0.001が条件であり,この条件がFalse,即ち,distance<=0.001になる手前までループが続く。

  • 1回目のループ

    • 2行目:distanceの値は1なのでdistance>0.001Trueを返す。条件が満たされたのでループの内容を実行する。

    • 3行目:print()関数を使い変数distanceの値を表示する。

    • 4行目:distance0.5に減少し,この時点でdistance=0.5にアップデートされる。

  • 2回目のループ

    • 2行目:distanceの値は0.5なのでdistance>0.001Trueを返す。条件が満たされたのでループの内容を実行する。

    • 3行目:print()関数を使い変数distanceの値を表示する。

    • 4行目:distance0.25に減少し,この時点でdistance=0.25にアップデートされる。

  • 3回目以降のループ

    • 同様の計算が行われ,distance<=0.001が成立するとループは停止する。

    • ループ停止後に#5が実行される。

ループ終了時のdistanceの値は0.001よりも小さいことが確認できる。もちろん,条件にある0.001を変えることにより,どこまで壁に近づけれるかを設定できる。もっと小さくすれば,より壁に近づくことができる。

上のwhileループは何回ループ計算をしたかは分からない。次のコードはそれを確認するために変数counterを追加している。

distance = 1
counter = 0

while distance > 1e-100:
    
    distance -= distance/2
    
    counter += 1
    
else:
    print(f'残りの距離は{distance}です。')
    print(f'{counter}回ループ計算しました🚀')
残りの距離は5.714936956411375e-101です。
333回ループ計算しました🚀

ここで1e-100\(10^{-100}\)である。

Note

科学的表記(scientific notation)は次のような書き方となる。

  • 1.5e0 = \(1.5\)

  • 1.5e10 = \(1.5\times 10^{10}\)

  • -8e100 = \(-8\times 10^{100}\)

  • 1e-100 = \(10^{-100}\)

  • -5.2e-10 = \(-5.2\times 10^{-10}\)

平方根の近似#

近似値の例として,紀元前数千年前のメソポタミア地域で発達したバビロン数学の例を考えよう。数字sの平方根(\(\sqrt{s}\))は次式で近似されることが知られている。

\[ x_{n+1}=\dfrac{1}{2}\left(x_n+\frac{s}{x_n}\right), \quad \text{初期値 }x_0, \quad n=0,1,2,3,..N \]

この式は数学で学ぶ漸化式の形となっている。\(N\)は任意の計算回数。\(N\)が大きくれば,それだけ誤差が小さくなり,\(x_n\)は真の値に近づいていく。実際にコードを書いてみよう。

def sqrt_approx(s, x0):  #1
    
    x = x0               #2
    
    while abs((x * x) - s) > 0.0001: #3
        
        x = (x + (s / x)) / 2        #4
        
        print(x)                     #5

<コードの説明>

  • #1:引数

    • s:平方根を計算する対象の数字

    • x0:漸化式の初期値

  • #2:初期値x0xに割り当てる。

  • #3abs()は絶対値を返す組込み関数である。引数のx*x-sは近似の誤差であり,誤差が0.0001よりも大きい間は漸化式の計算がおこなわれる。

  • #4:漸化式

    • 右辺は漸化式の右辺を表している。最初のループでは,\(x_n\)x0の値が代入される。その結果が左辺のxに割り当てられ(\(x_{n+1}\)に対応している),それと同時に#2x=x0は無効となる。

    • その後のループも同様の計算が続く。

  • #5xの値を表示する。

ではコードを実行してみるが,100を初期値として4の平方根を計算してみよう。

sqrt_approx(4,100)
50.02
25.04998400639744
12.604832373535453
6.4610854923746075
3.5400882555851294
2.335001794270127
2.0240312882070577
2.0001426615330145
2.000000005087715

紀元前に使われていたアルゴリズムとは信じられない程,真の値2への収束が速いことが伺える。

  • 上のコードで誤差は0.0001以下になっているが,この閾値を少しでも下回ると計算はストップする。より正確な近似を得るためにはより低い閾値を設定する必要がある。しかし,小さな誤差はより長い計算時間が必要になる。即ち,精度と計算速度にはトレード・オフが存在するということである。

  • Pythonは広く科学計算に使われ,(様々なパッケージを使うことにより)簡単に方程式の解を求めることができる。しかし,全てのケースで真の解そのものを計算できるとは限らない。この例の様に解を近似値として求める場合が多くあり,複雑な方程式であればなおさらである。計算過程で誤差が減少し解に収束していくことになるが,その収束速度が速い解法がより良いアルゴリズムだと言えるだろう。

if文とcontinue#

次のwhileループを考えてみよう。

counter = 0

while counter < 3:  #1

    counter += 1    #2
    
    print(counter)  #3
1
2
3

このコードは最初のwhileループの例と殆ど同じであり,異なるのは次の点だけである。

  • #2#3print(counter)counter+=1の位置を入れ替えている。

この2つの違いにより,最初に表示される数字は0ではなく1となり,最後に表示される数字は2ではなく3となっている。この違いは,フローチャートで示すとより分かりやすいのではないだろうか。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    flow.Box(w=2.2, h=1).label(r'counter = 0').drop('S')
    flow.Arrow().down(sd.unit/4)
    
    d = flow.Decision(w=3.5, h=1.5, S='True', E='False'
                     ).label('counter < 3 ?').drop('E')

    flow.Arrow().right(sd.unit/2).at(d.E)
    flow.Start(w=2, h=1).label('END').anchor('W')
    
    flow.Arrow().down(sd.unit/3).at(d.S)
    flow.Box(w=3., h=1).label('counter += 1')
    flow.Arrow().down(sd.unit/3)
    b = flow.Box(w=3.3, h=1).label('print(counter)')
    
    flow.Wire('c', k=-1.5, arrow='->').linewidth(1).at(b.W).to(d.W)
_images/16829766c636d636a6fdf13590f91919b68f18a69b0663e14b51fde0ea4e0477.svg

この例に基づいて,1から5までの数字を3を飛ばして1245を表示するとしよう。その場合に重宝するのがif文とcontinue(対象コードの実行を飛ばすコード)の組み合わせである。まず,フローチャートで表してみよう。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    flow.Box(w=2.2, h=1).label(r'counter = 0').drop('S')
    flow.Arrow().down(sd.unit/4)
    
    d1 = flow.Decision(w=3.5, h=1.5, S='True', E='False'
                     ).label('counter < 5 ?').drop('E')

    flow.Arrow().right(sd.unit/2).at(d.E)
    flow.Start(w=2, h=1).label('END').anchor('W')
    
    flow.Arrow().down(sd.unit/3).at(d.S)
    flow.Box(w=3., h=1).label('counter += 1')
    
    flow.Arrow().down(sd.unit/3)
    d2 = flow.Decision(w=3.5, h=1.5, S='False', W='True'
                 ).label('counter == 3 ?')
    flow.Arrow().left(sd.unit/2.1).at(d2.W)

    flow.Arrow().down(sd.unit/3).at(d2.S)
    b = flow.Box(w=3.3, h=1).label('print(counter)')
    
    flow.Wire('c', k=-1.5, arrow='->').linewidth(1).at(b.W).to(d1.W)
_images/33a1beea63dbee831177c0638010248226f6d70871cf916de57ea8d309d5baf8.svg

上のフローチャートと異なる点は,下の菱形が追加されていることだけであり,その菱形をif文で捉えることができる。更に,下の菱形から上の菱形への矢印(3の表示を飛ばす)をcontinueで実行することになる。コードを書いてみよう。

counter = 0

while counter < 5:    #1

    counter += 1      #2
    
    if counter == 3:  #3
        
        continue      #4
    
    print(counter)    #5
1
2
4
5

whileループの説明>

  • #1counter<5Trueである限り#2に進む。

  • #2counterの値が1増える。

  • #3

    • counter==3Falseの場合,#5に進む。

    • counter==3Trueの場合,#4に進む。

  • #4continueが実行されると,#1に戻る。即ち,#5は飛ばされる。

  • #5counterの値を表示する。

このようにwhileループでコードをスキップする場合にcontinueを使う。

Warning

  1. #2#4の下に置くと,counterの値は3でストップして無限ループになってしまうので注意しよう。

  2. #5#3の上に置くと,3も表示されるので注意しよう。

if文とbreak#

whileループでは

while True:
    <実行コード>

と書くと無限ループになってしまう。フローチャートで表現すると次のようになる。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    
    d = flow.Decision(w=3.5, h=1.5, S='True', E='False'
                     ).label('True ?')

    flow.Arrow(ls='dashed').right(sd.unit/2).at(d.E)
    flow.Start(w=2, h=1).label('END').anchor('W')
    
    flow.Arrow().down(sd.unit/3).at(d.S)
    b = flow.Box(w=2, h=1).label('実行コード')
    
    flow.Wire('c', k=-1.5, arrow='->').linewidth(1).at(b.W).to(d.W)
_images/e2e8df9d44c589e6b1be76326135628450513b6d8f4574a1a55d6b059eb4c69e.svg

菱形の中はTrueなので,必ずTrueの矢印の方向に沿ってコードは実行される。Falseも書いてあるが,そちらの矢印に決して進むことはない。

一方で,while True:と書くwhileループでも,途中で処理を終了させる方法がある。それがif文とbreak(ループを中断するコード)を組み合わせる方法である。フローチャートでは次のようになる。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)
    
    d1 = flow.Decision(w=3.5, h=1, S='True', E='False'
                     ).label('True ?')

    flow.Arrow(ls='dashed').right(sd.unit/2).at(d1.E)
    flow.Start(w=2, h=1).label('END').anchor('W')
    
    flow.Arrow().down(sd.unit/3).at(d1.S)
    b = flow.Box(w=2, h=1).label('実行コード')
    
    flow.Arrow().down(sd.unit/3.5)
    d2 = flow.Decision(w=3.5, h=1, S='True', W='False'
                 ).label('条件 ?')
    
    flow.Arrow().down(sd.unit/3.5)
    flow.Start(w=2, h=1).label('END')
        
    flow.Wire('c', k=-1.5, arrow='->').linewidth(1).at(d2.W).to(d1.W)
_images/6104f4c23dd4e367b979fbdc98d9a5d3ca16244454fd5082c947adad0d134bd1.svg

上のフローチャートと異なるのは,下の菱形(条件?)を追加することにより,whileループは維持するとともにENDへの矢印を追加した点である。

例を使って,この考えをコードに落とし込んでみよう。

counter = 0          #1

while True:          #2
    
    print(counter)   #3
    
    counter += 1     #4

    if counter > 2:  #5
        
        break        #6
0
1
2

whileループの説明>

  • #1:カウンター変数の初期化(0に設定する)

  • #2:条件はTrueなので,必ず#3以下のコードを実行する。

  • #3counterを表示する。

  • #4counterの値に1を加える。

  • #5

    • counter>2Falseの場合は#2に戻る。

    • counter>2Trueの場合は#6に進む。

  • #6breakを実行しループは終了となる。

フローチャートでは次のようになる。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)

    b = flow.Box(w=2.5, h=0.9).label('counter = 0')
    flow.Arrow().down(sd.unit/4)
    
    d1 = flow.Decision(w=3.5, h=1, S='True', E='False'
                     ).label('True ?')

    flow.Arrow(ls='dashed').right(sd.unit/2).at(d1.E)
    flow.Start(w=2, h=1).label('END').anchor('W')
    
    flow.Arrow().down(sd.unit/3).at(d1.S)
    b = flow.Box(w=3, h=1).label("print(counter)")

    flow.Arrow().down(sd.unit/4)    
    b = flow.Box(w=2.5, h=0.9).label('counter += 1')

    flow.Arrow().down(sd.unit/3.5)
    d2 = flow.Decision(w=3.5, h=1.2, S='True', W='False'
                 ).label('counter > 2 ?')
    
    flow.Arrow().down(sd.unit/3.5)
    flow.Start(w=2, h=1).label('END')
        
    flow.Wire('c', k=-1.5, arrow='->').linewidth(1).at(d2.W).to(d1.W)
_images/8186033819985b2de648d52b7a39c0ee76232638024519549a8b975519c68396.svg

上のコードの#4#6を少し書き換えて,次のようにしても同じ結果が返される。

counter = 0            #1

while True:            #2
    
    print(counter)     #3

    if counter < 2:    #4

        counter += 1   #5

    else:              #6

        break          #7
0
1
2

上のコードの方が可読性は高いように感じないだろうか。違いを理解するために,フローチャートを書いてみよう。

continuebreak#

continuebreakを一緒に使うことも可能である。1から5までの数字で3を飛ばして表示する次の例を考えよう。

counter = 0

while True:           #1

    counter += 1      #2
    
    if counter == 3:  #3
        continue      #4
        
    print(counter)    #5
    
    if counter > 4:   #6
        break         #7
1
2
4
5

whileループの説明>

  • #1:条件はTrueなので,必ず#2に進む。

  • #2counterの値に1を加える。

  • #3

    • counter==3Falseの場合,#5に進む。

    • counter==3Trueの場合,#4に進む。

  • #4continueが実行されると,#1に戻る。即ち,#5以下は飛ばされる。

  • #5counterの値を表示する。

  • #6

    • counter>4Falseの場合は#2に戻る。

    • counter>4Trueの場合は#7に進む。

  • #7

    • breakを実行しループを終了する。

continuebreakそれぞれにif文が必要となる。

フローチャートを使うと次のように表すことができる。

Hide code cell source
with schemdraw.Drawing() as sd:
    flow.Start(w=2, h=1).label('START')
    flow.Arrow().down(sd.unit/4)

    b = flow.Box(w=2.5, h=0.9).label('counter = 0')
    flow.Arrow().down(sd.unit/4)
    
    d1 = flow.Decision(w=3.5, h=1, S='True', E='False'
                      ).label('True ?')

    flow.Arrow(ls='dashed').right(sd.unit/2).at(d1.E)
    flow.Start(w=2, h=1).label('END').anchor('W')

    flow.Arrow().down(sd.unit/4).at(d1.S)  
    b = flow.Box(w=2.5, h=0.9).label('counter += 1')

    flow.Arrow().down(sd.unit/4)
    d2 = flow.Decision(w=3.8, h=1.2, S='False', W='True'
                      ).label('counter == 3 ?')
    flow.Arrow().left(sd.unit/2).at(d2.W)
    
    flow.Arrow().down(sd.unit/4).at(d2.S)
    flow.Box(w=3, h=0.9).label("print(counter)")

    flow.Arrow().down(sd.unit/4)
    d3 = flow.Decision(w=3.8, h=1.2, S='False', W='True'
                      ).label('counter >= 4 ?')

    
    flow.Arrow().down(sd.unit/3.5).at(d3.S)
    flow.Start(w=2, h=1).label('END')

    flow.Wire('c', k=-1.5, arrow='->').linewidth(1).at(d3.W).to(d1.W)
_images/dc1483c325f40f34216ec10c529d16cf89e56fbeb6f94d979e133d4e184da553.svg