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,
© Copyright 2024 ExpyDoc