■ 関数の定義(代入文形式) の節で,代入文形式のユーザ関数の定義を紹介した. この節では,より一般的な扱いを説明する.
より広い意味では,一連のプログラム片に名前を付けたものが,「関数(function)」である.
以下は,Hello
という文字を印字する関数の定義を示す.
関数定義は,キーワード function
で始まり,その後に関数名を書く.
呼び出す際に,渡すべき値がなければ,関数名の後に,括弧・閉じ括弧 ()
を書くだけである.
処理を記述した後,キーワード end
で終わる.
function hello()
println("Hello")
end
この関数を呼び出すには,関数名に続けて ()
を書けばよい.
hello()
hello()
関数を呼び出す際に,渡す値がある場合は,関数名の後に,
括弧 ()
で囲んで,変数名(仮引数,「かり・ひきすう」,parameter)の並びを書く.
function hello(name)
print("Hello ")
println(string(name))
end
この関数を呼び出すには,関数名に続けて ()
に囲んで,具体的な値(実引数「じつ・ひきすう」argument)を書く.
関数の定義の中に出現した仮引数は,実引数の値に置き換えられる.
hello("world")
hello("the Earth")
hello()
# 例外: 実引数 2つ
hello("world", "the Earth")
関数 hello
に対して,引数が 0個の場合の定義と,
引数が1個の場合の定義の2つが紐(ひも)ついている.
関数呼び出しにおける引数の数に応じて,対応する関数が実行される.
上の最後の例のように,対応する関数定義が見つからなければ,例外が発生する.
PyPlot
パッケージを読み込んだ状況で,円を描く関数を作成する.
引数は,円の中心座標 cx, cy
,円の半径 r
,線の色 col
とする.
function draw_circle(cx, cy, c, r)
t = 0:pi/18:2pi
xs = cx .+ r * cos.(t)
ys = cy .+ r * sin.(t)
plt.plot(xs, ys, color = c)
end
5つの円を描いてみよう.
using PyPlot
plt.figure() #hide
plt.axes().set_aspect("equal")
r = 1
s = 1.1
draw_circle(0, s, "k", r)
draw_circle(-s, 0, "y", r)
draw_circle(-2s, s, "b", r)
draw_circle(s, 0, "g", r)
draw_circle(2s, s, "r", r)
plt.savefig("ch12-mark1-plot.svg"); nothing; #hide
nothing; # don't clos #hide
すべての軸を描画しないようにするには,上に続けて plt[:axis]("off")
を実行すればよい.
plt.axis("off")
plt.savefig("ch12-mark2-plot.svg"); nothing; #hide
plt.close("all") #hide
!!! note オリンピックのシンボルマークは,知的財産権が厳しく保護されていることに留意されたい.
仮引数に続けて =値
を書くことで,既定の値を設定できる.
関数呼び出しで,該当する引数を省略して関数を呼び出した場合には,既定の値が用いられる.
関数 draw_circle
で,最後の引数 r
の既定値を 1
に設定した.
function draw_circle(cx, cy, c, r = 1)
t = 0:pi/18:2pi
xs = cx .+ r * cos.(t)
ys = cy .+ r * sin.(t)
plt.plot(xs, ys, color = c)
end
関数 draw_circle
を3つの引数で呼び出した場合には,r=1
として呼び出したとみなされる.
r = 1
s = 1.1
draw_circle( 0, s, "k")
draw_circle( -s, 0, "y")
draw_circle(-2s, s, "b")
draw_circle( s, 0, "g")
draw_circle( 2s, s, "r");
関数を実行した結果の値を,呼び出し側で使いたい場合は,しばしばある.
関数定義において,最後に評価した値が,関数の戻り値(return-value)となる.
■ 関数の定義(代入文形式) を用いても,数 x
を
triple(x)=3*x
これをより一般の形式で書くと,以下のようになる.最後に評価した 3*x
が関数の戻り値となる.
function triple(x)
3 * x
end
triple(-1)
キーワード return 式
は関数内で用いられると,直ちに関数から抜ける(戻る)ことを指示する.
この際,式の結果が関数の戻り値となる.
上の関数は,以下のようにも書ける(が,冗長である).
function triple(x)
return 3 * x
end
また,数 x
の絶対値を求める関数を自前で書いてみると,以下のようになる.
function myabs(x)
if x < 0
return -x
else
return x
end
end
myabs(-1)
!!! note
上の myabs
は ■ 3項演算子 を用いて,以下のように書いてもよい.
```
myabs(x)= x<0 ? -x : x
```
関数内部で代入した変数(「局所変数,local variables」)は,その関数内部でのみ生きている.
下の例で,関数 triple
内部で代入した t
は,関数 triple
のみで生きている.
コマンドラインで定義した変数 t
は,関数 triple
の局所変数 t
とは別物であり,関数呼び出し後も値は変わっていない.
コマンドラインで定義した変数 t
の型を,「全域変数(global variables)」ともいう.
t = 4
function triple(x)
t = 3
t * x
end
triple(2)
@show t;
ヘロンの公式(Heron's formula)によれば,
3辺の長さが
で与えられる.
ヘロンの公式を用いて,三角形の面積を返す関数を定義してみる.
三辺 0
を返すことにする.
function heron(a, b, c)
s = (a + b + c) / 2
r = s * (s - a) * (s - b) * (s - c)
if r < 0
return 0.0
end
sqrt(r)
end
実行してみよう.
heron(3, 4, 5)
# 三角形ができない場合
heron(3, 3, 7)
方形波をフーリエ級数の和として計算する方法を,▼ 方形波:フーリエ級数の有限和 の節で,紹介した.
時刻の配列 ts
における方形波を,奇数
function square(ts, n)
ys = zeros(size(ts))
for i = 1:2:n
ys += sin.(i * ts) / i * 4 / pi
end
ys
end
実行してみよう.
@show square((0:6) * pi / 4, 13);
級数和の上限
using PyPlot
plt.figure() #hide
ts = -74*pi/36:pi/36:74*pi/36
plt.plot(ts, sign.(sin.(ts)), label = "square wave")
for n in [5, 9, 13]
plt.plot(ts, square(ts, n), label = "up to " * string(n))
end
plt.legend(loc = "center right")
plt.yticks([-1, 0, 1], ["-1", "0", "1"])
plt.xticks([-2pi, -pi, 0, pi, 2pi], [L"-2\pi", L"-\pi", "0", L"\pi", L"2\pi"])
plt.savefig("ch12-sqwave-plot1.svg"); nothing; #hide
plt.close("all") #hide
上の関数 square
を以下のように改良せよ.
-
$n$ が偶数の場合でも,適切な解釈に基づき動作するようにせよ. -
$n$ が 3 よりも小さい場合はn=13
とせよ.
関数の定積分の近似値を,短冊の面積の和として計算する方法を ▼ Riemann和(繰り返しで加算)の節で,紹介した.
関数 f
,定積分の範囲 a, b
,分割数 n
を引数として,Rienmann和を計算する関数を書いてみる.
function riemann_sum(f, a, b, n)
xs = range(a, b, length = n + 1)
d = (b - a) / n
s1 = 0
for i = 1:n
x = xs[i]
s1 += g(x) * d
end
s1
end
実行してみよう.
g(x) = 1 / (1 + x)
a = 0;
b = 1;
@show riemann_sum(g, a, b, 2^10);
分割数を増やす.
for m = 8:13
@show m, riemann_sum(g, a, b, 2^m)
end
@show log(2);
上の例で,相対誤差を描け.
上の関数 riemann_sum
を,以下のように改良せよ.
-
$n < 1$ の場合には,ただちに0
を返すようにせよ. -
$n < 1$ の場合には,$n=4$ として,計算せよ. -
$a > b$ の場合には,上限と下限を交換してから計算せよ.ヒント:変数$a, b$ の値を交換するには,同時代入b, a = a, b
を用いよ.
平面内のある領域の面積の近似値をモンテカルロ法で求める方法を,▼ モンテカルロ法による平面図形の面積の推定 で紹介した.
範囲を表す関数 f
,点の数 n
を引数として,モンテカルロ法で面積の近似値を計算する関数を書いてみる.
関数 f
は,平面座標 x,y
を引数にして,(x,y)
が図形の内部なら真 true
を,内部でなければ偽 false
を返すものとする.
なお,図形の範囲は,$x$ 座標,$y$ 座標とも,$0$ から
function montecarlo(f, n)
s = 0
for i = 1:n
x = rand()
y = rand()
if f(x, y)
s += 1
end
end
s / n
end
四分円に対して,実行してみよう.
quadrant(x, y) = x * x + y * y < 1
n = 2^8
@show n, montecarlo(quadrant, n), pi / 4
点の数を増やしてみる.
for m = 1:10
n = 2^m
@show n, montecarlo(quadrant, n)
end
今度は,二つの不等式 y < x
と y < 1 − x
の両方に囲まれる領域の面積を推定しよう.
tri1(x, y) = y < x && y < 1 - x
n = 2^8
@show n, montecarlo(tri1, n)
点の数を増やす.
for m = 4:12
local n = 2^m
@show n, montecarlo(tri1, n)
end
@show 1 / 4
上の2つの例で,相対誤差を描け.
上の関数 montecarlo
を,以下のように改良せよ.
-
$n \le 1$ の場合には,$n=2^8$ として,計算せよ.
■ タプル の小節で,関数 divrem
のように,複数の値を返す関数があることを紹介した.
ユーザ関数から複数の値を返すには,関数定義の最後に評価する式において,カンマ ,
で区切って復数の式を書けばよい.
この関数の戻り値は,■ タプル となる.
function one_two()
1, 2
end
@show one_two()
x, y = one_two()
@show x, y
「はさみうち」法を用いて,方程式の解の存在範囲を狭めていく方法を,▼ 「はさみうち」法による,方程式の求解 で紹介した.
求めるべき方程式 f
,解の存在範囲の下限 a
,および 上限 b
を引数として,より狭い解の存在範囲の下限と上限を返す関数を書いてみる.
function bisect(f, a, b)
c = (a + b) / 2
if f(a) * f(c) > 0
a = c
else
b = c
end
a, b
end
g(x) = x^3 + 3x^2 - 4 * x - 12
a = -3.2;
b = -2.6;
for i = 1:10
global a, b
@show a, b, b - a, g(a), g(b)
a, b = bisect(g, a, b)
end
- 上の例で,解の存在範囲が狭くなる様子を描け.
- 異なる解の存在範囲に対して,「はさみうち」法を実行せよ.
関数定義の仮引数には ::型
という形式で,仮引数の型を指定できる.
関数呼出しの際,実引数の型と仮引数の型が一致する関数が呼び出される.
この仕組を,多重ディスパッチ(multiple dispatch)という.
型が指定されていない仮引数は,Any
型とみなされ,あらゆる実引数の型と一致する.
個別の仮引数に対応する関数は,メソッドと呼ばれる.
例で説明する.
関数 mytest
を,Int64
型の引数に対してだけ定義しよう.
function mytest(x::Int64)
println(string(x) * " is of Int64 type")
end
この段階で,mytest(1)
は上の関数が呼び出されるが,mytest(1.0)
は呼び出されるべき関数が見つからず,例外が発生する.
# 関数が呼び出される
mytest(1)
# 関数が呼び出されない
mytest(1.0)
関数 mytest
を,Float64
型の引数に対しても定義する.
function mytest(x::Float64)
println(string(x) * " is of Float64 type")
end
# 今度は,上の関数が呼び出される
mytest(1.0)
しかし,mytest("a")
や mytest("[1]")
は呼び出されるべき関数が見つからず,例外が発生する.
# 関数が呼び出されない
mytest("a")
# 関数が呼び出されない
mytest([1])
更に,仮引数に型を指定しない関数を定義すれば,そちらが呼び出される.
function mytest(x)
println(string(x) * " is neither of Int64 type nor of Float64 type")
end
# 今度は,上の関数が呼び出される
mytest("a")
# 今度は,上の関数が呼び出される
mytest([1])
1 から整数 n
までの連続する整数の積を,「階乗(factorial)」といい,$n!$ のように書く.
さらに,$0! = 1$ と定義する.
階乗を計算する関数を定義しよう. 階乗関数は整数に対してのみ定義されるから,引数を整数型に限定しよう.$0$ よりも小さい整数に対して,階乗は定義されないが,$1$ を返すことにする.
function myfact1(n::Int64)
n <= 1 && return 1
r = 1
for i = 2:n
r *= i
end
r
end
正しく計算されることを確かめてみる.
for n = 6:-1:-1
@show n, myfact1(n)
end
浮動小数点数や整数ベクトルに対して,関数 myfact1
は未定義である.
# 関数は未定義
myfact1(2.2)
# 関数は未定義
myfact1([6, 3])
整数を要素とするベクトルに対して dot記法を用いて myfact1
を呼び出せば,
各要素に対して関数 myfact1
を呼び出した値のベクトルが得られる.
myfact1.([6, 5, 4])
!!! note
Julia には,階乗関数 factorial(n)
が組み込まれている.
factorial(1)
factorial(2)
factorial(3)
階乗には
これをプログラムで書く場合には,関数定義の中で自分自身を呼ぶ,これを再帰(recursion)という.
再帰を用いて,階乗を定義してみよう.
function myfact2(n::Int64)
n <= 1 && return 1
n * myfact2(n - 1)
end
正しく計算されることを確かめてみる.
for n = 6:-1:-1
@show n, myfact2(n)
end
再帰呼出しを用いる場合には,計算が正しく終了する条件を設定する必要がある.
フィボナッチ(Fibonacci)数とは,以下の漸化式で定義される数列である.
整数
フィボナッチ数の最初の 20 個は,以下の通りである.
また,フィボナッチ数の一般項は,以下の式で与えられることが知られている.合わせて,計算してみよ.
!!! note 上で示した階乗やフィボナッチ数の例では,関数が呼ばれる毎に計算を行っており「もったいない」. 計算結果が一意(一通り,unique)であるなら,計算した結果を記録しておき,同じ引数で再び呼び出されたときには, 記録から取り出してくれば計算資源が節約できる.このような考え方を「メモ化」という.この本文で説明した範囲でも, ベクトルなどを用いて「メモ化」を実装できるであろう.
- 関数
- 定義
- 呼出し
- 戻り値
- 仮引数の型
- 再帰呼出し
- 例題
- ヘロンの公式
- フーリエ級数和の関数化
- モンテカルロ法の関数化
- はさみうち法の関数化
- 階乗
- フィボナッチ数