Document

2. 関数を使ったプログラムの
作成と実行
プログラミング論I
本日の内容
•
(前回) Scheme の式と関数の書き方の基礎
とその評価結果に至る過程、およびエラー
•
関数を使ったプログラムの作成法の基礎:
–
複数の関数から構成する目的や利点など
–
設計レシピ :プログラム開発のガイドライン
複数の関数を使ったプログラム
仕事の分割
この仕事には
△△と○○の
結果が必要だ
…
△△の仕事を
頼む!
○○の仕事を
頼む!
上司
○○の結果
△△の結果
△△の開始に
必要なデータ
○○の開始に
必要なデータ
担当者B
担当者A
プログラムの分割と関数
通常プログラムは一つだけでなく数多くの定義からなる
例えば関数定義に関して:
• 主関数(Main Function):
– 本当に必要な最終結果を生成する関数
• 補助関数(Auxiliary Function):
上司
担当者A
– より容易に解決できるよう分割した部分問題を解く
関数 (この部分問題の解を使い,最終結果を求める)
– 可読性(思考過程の再現性)や再利用性を高める
※プログラムによっては,ある主関数が別プログラムの補助関数になることもあるし,
その逆もある
複雑で大きなプログラムに補助関数の使用は必須
プログラムの分割と関数
Cの処理を頼む!
Bの処理を頼む!
(主)関数A
結果
結果
呼び出す側
必要なデータ
(補助)関数B
必要なデータ
(補助)関数C
呼び出される側
呼び出される側
プログラムは,しばしば,複数の関数に
「分割」される
例題1. 2乗の和
• a と b とから,a2+b2 を求めるプログラムを
関数 sum-of-squares として作成し実行
• 手順を分けて考える
– まず2乗について:ある値 x から x2 を求める
補助関数 square を考える
– 主関数では、補助関数 square を2回使い
2乗の値を2つ求め、それらを足す
関数squareの入力と出力
結果:
16
xの値:
4
square
入力は
1つの数値
出力は
1つの数値
関数 square の定義
「(関数の)定義である」
ことを示すキーワード
この関数の名前
(define (square x)
1つの関数定義
(* x x))
x の値から (* x x)
を計算(出力)
値を1つ受け取る(入力)
関数sum-of-squaresの入力と出力
x, y の値:
2, 4
結果:
20
sum-of-squares
入力は
2つの数値
出力は
1つの数値
関数sum-of-squaresの定義
「(関数の)定義である」
ことを示すキーワード
この関数の
名前
sum-of-squares (a, b)
= square (a) + square (b)
(define (sum-of-squares a b)
(+ (square a) (square b)))
1つの
関数定義
値を2つ受け取る(入力)
「a2 + b2 」
を計算(結果として出力)
自分で定義した関数を使って,関数定義ができる!
2乗の和のプログラム
• a2+b2 を求める
squareの定義
(define (square x) (* x x))
(define (sum-of-squares a b)
(+ (square a) (square b)))
sum-of-squaresの定義
注: 引数名は各関数内のみ有効。
例えば右のsum-of-squares内
の x と square内の x は字面は
同じだがそれぞれの関数定義に閉
じた異なる変数。
(define (square x) (* x x))
(define (sum-of-squares x y)
(+ (square x) (square y)))
関数の間の関係
主関数 sum-of-squares
(define (sum-of-squares a b)
(+ (square a) (square b)))
補助関数 square
(define (square x)
(* x x))
sum-of-squares 関数の中で,
square 関数を使っている(2箇所)
データの流れ
主関数 sum-of-squares
(define (sum-of-squares a b)
(+ (square a) (square b)))
④ これらの結
果を使い最終
結果を計算
同様に①(渡すのは
yの値), ②, ③を行う
square関数
① square関数に値aを
渡し,aの2乗の計算を依頼
③実行結果を呼び出しもとの
sum-of-squares関数に返す
(define (square x)
(* x x))
② 変数xを渡された値aで
置換えて本体式a2を計算
関数の分割に関して
分割しない例
(define (sum-of-squares x y)
(+ (* x x) (* y y)))
どちらのsum-of-squares
分割する例
も機能的には同一
(define (square x)
可読性(思考過程の明示)
(* x x))
再利用性などを考慮し、
(define (sum-of-squares a b)
分割するかを判断
(+ (square a) (square b)))
(sum-of-squares 1 2)
double square(double x){
C言語での定義の例
return x*x;
}
double sum_of_squares(double a, double b){
return square(a)+square(b);
}
(sum-of-squares 20 30)から1300が得られる過程
最初の式
(sum-of-squares 20 30)
= (+ (square 20) (square 30))
= (+ (* 20 20) (square 30))
= (+ 400 (square 30))
(sum-of-squares a b) の本体
(+ (square a) (square b)) を呼
び出しa=20,b=30の置換を行う
(square x) の本体 (* x x)
を呼び出し x=20 の置換
(* 20 20) → 4
(square x) の本体 (* x x) に x=30 の置換
= (+ 400 (* 30 30))
= (+ 400 900)
= 1300
実行結果
(* 30 30) → 900
コンピュータ内部での計算
練習問題1
• x 円の硬貨 y 枚の金額を求める関数 coins を作れ。
• 関数 coins を用い、1円硬貨 a 枚、5円硬貨 b 枚、10
円硬貨 c 枚、50円硬貨 d 枚、100円硬貨 e 枚、500円
硬貨 f 枚の合計を求める関数 total を作れ。
(define (coins x y)
(* x y))
(define (total a b c d e f)
(+ (coins 1 a) (coins 5 b) (coins 10 c)
(coins 50 d) (coins 100 e) (coins 500 f)))
;; test
>(total 1 2 3 4 5 6)
>(+ (coins 1 1) (coins 5 2) (coins 10 3)
(coins 50 4) (coins 100 5) (coins 500 6))
>(+ (* 1 1) (coins 5 2) ... )
設計レシピによるプログラム作成
プログラム設計法
プログラム開発には,多くのステップが必要
– 問題記述の文中で何が本質で重要(不要,冗長)か?
– 入力に何を受け取り,出力にどう関連づけるか?
– 対象データに対する演算を,Schemeの処理系が提供し
ているか?
⇒なければ対応するプログラムを開発
– 作成後、実際に意図した計算を実行するか?
をチェック
体系的に秩序正しく開発 → 設計レシピ
– 何をすべきかのステップごとの処方箋とその順序
– 少なくとも4つの活動:purpose,example,definition,test
プログラム設計法
プログラム開発に必要な活動
1. Purpose
• プログラムの目的を理解し、機能の概要を記述
•
•
•
Contract:関数名とデータ受け渡しに関する約束を記述
Header:関数定義の先頭部(名前や引数)を記述
Statement:何を計算するか自然言語や数式でコメント
2. Example
• プログラムの振る舞いを「例」で記述.
3. Definition
• プログラムの定義を具体化する
4. Test
• 検査を通じた誤り(エラー)の発見
例題2. リングの面積
• 外径 outer, 内径 inner からリングの面積を求め
るプログラム area-of-ring を作り実行する
まず問題をよく分析して理解する
真ん中に穴のあいた
円板の面積と考える
inner
求める面積は,外側の円の面積から,
内側の穴の円の面積を引いたもの
– 円の面積を求める関数 area-of-disk を使う
outer
リングの面積
外径:outer
内径: inner
inner
outer
リングの面積
= 外側の円の面積 - 内側の円の面積
半径 outer の円
半径 inner の円
(外径 outer と内径 inner は実際の計算時に与えられる)
入力と出力
5, 3
50.24
area-of-ring
入力は
1つの数値
inner, outer
出力は
1つの数値
Purpose - contract & header
• 問題の解析と目的の把握
• 関数の名前と取り扱うデータの種類の記述
area-of-ring: number number -> number
関数名(内容を適切に表現) : 入力データの種類の列挙 -> 出力
プログラム中での記述
;; area-of-ring : number number -> number
ここで ; で始まる行は実行されない(人間が読むコメント)
引数名を決めれば関数ヘッダ(先頭部)も書ける
;; area-of-ring : number number -> number
(define (area-of-ring outer inner) ...)
Purpose - statement • プログラムの振る舞いに関する仕様の明確化
– プログラムの仕様,目的など
to compute the area of a ring whose radius is
outer and whose hole has a radius of inner
プログラムが何をするか,何を計算するかを書いた文章
(注:DrRacket では英文で入力)
プログラム中での記述
;; area-of-ring : number number -> number
;; to compute the area of a ring whose radius is
;; outer and whose hole has a radius of inner
(define (area-of-ring outer inner) ...)
Purpose での成果
• 以下順に
– contract:関数の名前と取り扱うデータの種類
– statement:プログラムが何をするかのコメント。header
の引数名をコメント中に盛り込むと,よりわかりやす
い
– header:関数ヘッダ(先頭部)。本体式はまだない
;; area-of-ring : number number -> number
;; to compute the area of a ring whose radius is
;; outer and whose hole has a radius of inner
(define (area-of-ring outer inner) ...)
Example
• 例題を使った,プログラムの振る舞いの例示
• 入出力関係を特徴づけるような「例」での記述
area-of-ring should produce 50.24 for the inputs 5 and 3
入力と期待される出力
(プログラムの理解の助けになる)
プログラム中の記述
;;area-of-ring : number number -> number
;;to compute the area of a ring whose radius is
;;outer and whose hole has a radius of inner
;;example:(area-of-ring 5 3) should produce 50.24
(define (area-of-ring outer inner) ...)
Example の効用
特定の入力に対する出力の記述
1. プログラムを正確に理解する手段
(関数の入出力関係や,計算過程など)
2. 論理的なエラー発見の手段.
•
プログラムの本体を書き下す前に例を作る
 記述後ではプログラム記述の影響を受ける可能性有
3. 後でプログラムを読み返すときのためのメモ
•
プログラム作成者以外にも有用)
Definition
• Example まで出来たらプログラム本体の記述を開始.
• Header において「…」としていた部分を実際に作成
ドーナツ型の面積は外側の円の面積から内側の円の
面積を引いたもの
プログラムの振る舞い
プログラム中での記述
;;area-of-ring : number number -> number
;;to compute the area of a ring whose radius is
;;outer and whose hole has a radius of inner
;;example:(area-of-ring 5 3) should produce 50.24
(define (area-of-ring outer inner)
(- (area-of-disk outer)
(area-of-disk inner)) )
area-of-ring 関数の定義
「(関数の)定義である」
ことを示すキーワード
関数の名前
(define (area-of-ring outer inner) 1つの
(- (area-of-disk outer)
関数
(area-of-disk inner)))
定義
「外側の円の面積
- 内側の円の面積」を
計算(出力)する本体式
値を2つ受け取る(入力)
Definition
• 記述していた「Example」を参考に,与えられた
入力から,プログラムがどのように出力を計算
するのかを理解した後に行う
• 基本演算や定義済(予定)のSchemeプログラム
を使い,入力引数から答を計算
– 入出力関係が,数式で与えられていれば,
→ 直ちにプログラムを書ける
– 言葉で問題が与えられていれば,
→ 注意深くプログラムを作る
プログラム設計法の例(まとめ)
;;Contract:
;;area-of-ring : number number -> number
(define (area-of-ring outer inner) … )
;;Purpose(statement):
;;to compute the area of a ring whose radius
;;is outer and whose hole has a radius of inner
;;Example:
;;(area-of-ring 5 3) should produce 50.24
;;Definition:
(define (area-of-ring outer inner)
(- (area-of-disk outer)
(area-of-disk inner)))
;;Tests:
(area-of-ring 5 3) ;; expected value 50.24
参考 Web ページ
http://www.htdp.org/2003-09-26/Book/curriculum-Z-H-5.html#node_sec_2.5
C言語での例
//Contract:
//area_of_ring: double double-> double
//Purpose(statement):
//to compute the area of a ring whose radius
//is outer and whose hole has a radius of inner
//Example:
//area_of_ring(5,3) should produce 50.24
double area_of_ring(double outer, double inner)
{
return (area_of_disk(outer)
- area_of_disk(inner));
}
//Tests:
void main(){
printf(“%f”,area_of_ring(5, 3));
}
実行例: (area-of-ring 5 3)から 50.24が得られる過程
最初の式
area-of-ring の本体
(- (area-of-disk outer)
(area-of-disk inner))
(area-of-ring 5 3)
に outer= 5, inner= 3 の置換
コンピュータ内部での計算
= (- (area-of-disk 5) (area-of-disk 3))
= (- (* 3.14 (* 5 5)) (area-of-disk 3))
= (- (* 3.14 25) (area-of-disk 3))
= (- 78.5 (area-of-disk 3))
= (- 78.5 (* 3.14 (* 3 3)))
= (- 78.5 (* 3.14
= (- 78.5 28.26)
= 50.24
実行結果
9))
area-of-disk 本体
(* 3.14 (* r r))
に r = 5の置換
(* 5 5) → 25
(* 3.14 25) → 78.5
area-of-disk 本体
(* 3.14 (* r r))
に r = 3の置換
(* 3 3) → 9
(* 3.14 9) → 28.26
※円周率PIは
3.14とする
ここまでのまとめ
1. Purpose:
プログラムの目的を理解し、機能の概要を記述
– Contract:関数名,データ受渡しに関する約束を記述
– Header:関数定義の先頭部(名前や引数)を記述
– Statement:何を計算するか自然言語や式でコメント
2. Example
プログラムの振る舞いを「例」で記述.
3. Definition
プログラムの定義を具体化する
4. Test
検査を通じた誤り(エラー)の発見
例題3. 映画館の利益計算
テキスト3.1節の例題:
「映画館」の所有者がチケット価格を決める時,
チケット価格と利益の間の関係を知りたい
チケット価格から,収入,支出,利益などを見積
もりたい (高価格なら入場者減のトレードオフ)
• チケット価格と平均観客との間の関係
– チケットあたり5.00ドルの価格では、120人
– 10セント(0.10ドル)下げると観客が15人増える
• 観客の増加は支出の増加につながる
– 上映ごとに180ドルの固定費
– 各観客ごとに4セント(0.04ドル)の費用
例題5. 映画館の利益計算
まず目的を理解し機能の概要を記述する Purpose フェーズ
今回はいくつかの数量が互いに依存 → 依存関係を一つずつ分析
(視点:自由に設定できるチケット代をいくらにするか決めたい)
• 利益は ticket-price に依存 (∵ 収入と支出の双方が依存)
;; profit : number -> number
;; to compute the profit as the difference between
;; revenue and costs at some given ticket-price
(define (profit ticket-price) ... )
• 収入も ticket-priceに依存 (∵収入を決める観客数もそれに依存)
;; revenue:number → number
;; to compute the revenue, given ticket-price
(define (revenue ticket-price) ... )
例題5. 映画館の利益計算
目的を理解し機能の概要を記述する Purpose フェーズ
いくつかの数量が互いに依存 → 依存関係を一度に一つずつ分析
(視点:自由に設定できるチケット代をいくらにするか決めたい)
• 支出もticket-price依存 (∵ 支出の算出に必要な観客数が依存)
;; cost : number -> number
;; to compute the cost, given ticket-price
(define (cost ticket-price) ... )
• 観客数も ticket-price 依存 ( ticket-price と観客数には相関あり)
;; attendees:number → number
;; to compute the number of attendees,
;; given ticket-price
(define (attendees ticket-price) ... )
例題5. 映画館の利益計算
各関係ごとに数量が互いにどう依存するかを分析し定式化
→ チケット代 ticket-price から利益(と収入、支出、観客数)を求める
関数 profit, revenue, cost, attendees の計算例と本体作成
– profit:
利益 = 収入(revenue) - 支出(cost)
– revenue: 収入 = 観客数(attendees) × チケット代(ticket-price)
– cost:
支出 = 固定費 + 観客数(attendees) ×観客ごとの費用
• 固定費: $180, 観客ごとの費用: 観客1人あたり $0.04
– attendees: チケット代(ticket-price)と観客数には関係がある
• チケット代:$5 のとき観客数120人,$0.1値下げで15人増加
支出の見積もり式
支出
観客数に比例して
かかる部分
固定費
観客がいなくても
かかる費用
(会場,設備,宣伝,
出演料その他)
0
観客数
• 支出= 固定費 + 観客数 × 観客ごとの費用
今回)
固定費: $180
観客ごとの費用: 観客1人あたり $0.04
観客数の見積もり式
観客数
推定された見積もり式
0
チケット代
• チケット代と観客数には関係がある
今回) チケット代:$5 のとき,観客数は120人だった
チケット代:$0.1値下げすると15人増えた
⇒ 観客数 = -(15/0.1)×(チケット代-$5)+120 と見積もる
例題3. 映画館の利益計算:例
分析結果にもとづき詳細定義の前に example を考える
;; profit : number -> number
;; ...
;; example: (profit 5) should produce 415.2
(define (profit ticket-price) ... )
;; revenue:number → number
;; ...
;; example: (revenue 5) should produce 600
(define (revenue ticket-price) ...)
;; cost : number -> number
;; ...
;; example: (cost 5) should produce 184.8
(define (cost ticket-price) ...)
;; attendees:number → number
;; ...
;; example: (attendees 5) should produce 120
(define (attendees ticket-price) ...)
例題3. 映画館の利益計算:定義
(define (profit ticket-price)
(- (revenue ticket-price)
(cost ticket-price)))
(define (revenue ticket-price)
(* (attendees ticket-price)
ticket-price))
(define (cost ticket-price)
(+ 180
(* (attendees ticket-price)
0.04)))
(define (attendees ticket-price)
(+ 120
(* (/ 15 0.10)
(- 5.00 ticket-price))))
profit 関数
(スペースの都合上
ヘッダなどは省略)
revenue 関数
cost 関数
attendees 関数
C言語での例
// profit: double -> double
double profit(double ticket-price){
return revenue(ticket-price)
-cost(ticket-price);
}
// revenue: double -> double
double revenue(double ticket-price){
return attendees(ticket-price)
*ticket-price;
}
// cost: double -> double
double cost(double ticket-price){
return 180+(attendees(ticket-price)
*0.04);
}
// attendees: double -> double
double attendees(double ticket-price){
return
120+(15/0.10)*(5.00-ticket-price);
}
void main(){ printf(“%f”, profit(4.5)); }
profit関数を起点にした呼出関係
profit 関数
(define (profit ticket-price)
(- (revenue ticket-price)
(cost ticket-price)))
revenue 関数
revenue,cost
cost 関数
関数を呼び出し
(define (cost ticket-price)
(+ 180
(* (attendees ticket-price)
0.04)))
attendees 関数
を呼び出し
(define (revenue ticket-price)
(* (attendees ticket-price)
ticket-price))
attendees 関数
を呼び出し
attendees 関数
(define (attendees ticket-price)
(+ 120
(* (/ 15 0.10)
(- 5.00 ticket-price))))
練習2
• 関数 profit (授業の例題3)についての練習
– チケット代が 3, および 4 の時の関数 profit の
実行結果を示せ
(profit 3)から 1063.2 が得られる過程
最初の式
(profit 3)
= (- (revenue 3) (cost 3))
= (- (* (attendees 3) 3) (cost 3))
= (- (* (+ 120 (* (/ 15 0.10) (- 5.00 3))) 3) (cost 3))
= (- (* (+ 120 (* 150 (- 5.00 3))) 3) (cost 3))
= (- (* (+ 120 (* 150 2)) 3) (cost 3))
= (- (* (+ 120 300) 3) (cost 3))
= (- (* 420 3) (cost 3))
= (- 1260 (cost 3))
= (- 1260 (+ 180 (* 0.04 (attendees 3))))
= (- 1260 (+ 180 (* 0.04 (+ 120 (* (/ 15 0.10) (- 5.00 3))))))
= (- 1260 (+ 180 (* 0.04 (+ 120 (* 150 (- 5.00 3))))))
= (- 1260 (+ 180 (* 0.04 (+ 120 (* 150 2)))))
= (- 1260 (+ 180 (* 0.04 (+ 120 300))))
= (- 1260 (+ 180 (* 0.04 420)))
= (- 1260 (+ 180 16.8))
コンピュータ内部での計算
= (- 1260 196.8)
=1063.2
実行結果
課題1
• 関数 profit (授業の例題3)についての問題
– 業務内容の変更で固定費が $0 、一人当たりの
支出が $1.5 となった。対応するよう例題3 のプ
ログラムを変更しなさい。
– この場合のチケット代を 3, 4, 5 としたときの関数
profit の実行結果を報告しなさい
cost: 支出=固定費+観客数(attendees) ×観客ごとの費用
固定費: $10, 観客ごとの費用:観客1人あたり $0.5
(define (cost ticket-price)
(+ 10 (* (attendees ticket-price) 0.5) ) )
ところで
• 複数の関数に分割しなかったら,
どうなっているだろう?
直接,profitを計算するような式を書いてみる
;; profit : number -> number
;; to compute profit, given ticket-price
(define (profit ticket-price)
(- (* (+ 120 (* 15 (/ (- 5 ticket-price) 0.1))) ticket-price)
(+ 10 (* (+ 120 (* 15 (/ (- 5 ticket-price) 0.1))) 0.5))))
ところで,以下の式のエラーを,すぐに見つけることが
できるだろうか?
;; profit : number -> number
;; to compute profit, given ticket-price
(define (profit ticket-price)
(- (* (+ 120 (* 15 (/ (- 5 ticket-price) 0.1)) ticket-price))
(+ 10 (* (+ 120 (* 15 (/ (- 5 ticket-price) 0.1)) 0.5)))))
直接,profitを計算するような式をC言語でも書いてみる
//profit : double -> double
// to compute profit, given ticket_price
double profit (double ticket_price){
return (120+15*((5-ticket_price)/0.1))*ticket_price)
-(10+(120+(15* ((5-ticket_price)/0.1)))*0.5);
}
ところで,以下の式のエラーを,すぐに見つけることが
できるだろうか?
見つけ難いのは,
言語のせいではない
//profit : double -> double
// to compute profit, given ticket_price
double profit (double ticket_price){
return (120 + (15 * ((5 - ticket_price) / 0.1))*ticket_price)
-(10 + (120 + ((15 * (5 - ticket_price) / 0.1)))*0.5);
}
せめてattendees関数だけでも利用するprofit関数の式を書いてみる
;; attendees : number -> number
;; to compute the number of attendees, given ticket-price
(define (attendees ticket-price)
(+ 120 (* 15 (/ (- 5 ticket-price) 0.1))))
少しはすっきりするが,
revenueとcostの計算式が
直接,表れている.
;; profit : number -> number
;; to compute profit, given ticket-price
(define (profit ticket-price)
(- (* (attendees ticket-price) ticket-price) ;; revenue
(+ 10 (* (attendees ticket-price) 0.5)))) ;; cost
課題2
• 合衆国は以下のように、世界標準とは異なる単位制度
を使っている:
合衆国
世界標準
1 inch
= 2.54 cm
1 foot
= 12 in.
1 yard
= 3 ft.
1 rod
= 5.5 yd.
1 furlong
= 40 rd.
1 mile
= 8 fl.
– 以下の関数を作れ:inch->cm, feet->inches, yards>feet, rods->yards, furlongs->rods, miles->furlongs
その後に
– 以下の関数を作れ:feet->cm, yards->cm, rods->inches,