授業資料/05 の変更点


#contents

* 前回の課題について [#i2f10897]

前回の総仕上げは,「2 から与えられた数までの整数について,すべて素数かどうか判定して出力せよ」という問題であった.
よく考えると,素数判定プログラムは既に知っているのであるから,これを利用すれば良い.
つまり,全体の構造としては以下のようになるはずだ.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
inluce Math

last_n = ARGV[0].to_i
for n in 2..last_n do

(n が素数かどうか判定して print する.このプログラムは以前のものが使える)

end
}}
そして,「n が素数かどうか判定して print する」プログラムの本体部分は
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
(n が与えられているとして)
isPrime = " is a prime number."
upper = sqrt(n).floor
for i in 2..upper do
  if (n % i == 0) then
    isPrime = " is not a prime number."
  end
end 
print(n,isPrime,"\n")
}}
という感じだった(''p'' での出力を,先週の知識に合わせて ''print'' に直してある).
&ref(/materials/warning.png); これ以降は,原則として ''p'' は使わずに ''print'' を使おう.

であるので,あとはこれを何も考えずに上の全体構造にコピペするだけで,次の完成形が得られる.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
inluce Math

last_n = ARGV[0].to_i
for n in 2..last_n do

  isPrime = " is a prime number."
  upper = sqrt(n).floor
  for i in 2..upper do
    if (n % i == 0) then
      isPrime = " is not a prime number."
    end
  end 
  print(n,isPrime,"\n")

end
}}

** ポイント [#h0c1df07]
このように,
CENTER:&size(24){''前回の課題はわかってみれば簡単すぎる.''};

しかし,実際には苦しんだ学生も多かろう.それは,
CENTER:&size(24){''プログラミングには,全体を機能の集まりで構造化する発想が必要''};

だからである.

聡明な人間はものごとを構造化して捉えることが自然に出来ているので問題ないが,まあ,出来ていない学生もいるだろう.前回の課題が難しいと感じた学生は,これからの授業で「全体の構造を捉える/考える」ことを意識してプログラミングをすすめるようにしよう.

** インデント(行頭からの字下げ): 細かいテクニック [#sf6852c1]

&ref(/materials/warning.png); 上のサンプルで,''for''文や ''if'' 文の中身が適切に「行頭から字下げ(インデント)」されていることに着目しよう.
これは''「その文の有効範囲」''を示す,ベタなテクニックである.
そして,これによって,インデントを見ただけでプログラムの構造がわかる.こうしておけば,「end がありません」というエラーや,「どこまでが for の範囲だろう」などの悩みなどと無縁になれる.つまり,
CENTER:&size(24){''適切なインデントが,まっとうなプログラミングの第一歩''};

なのである.

インデントに手間がかかるのでは? という疑問もあるだろうが,エラーに悩む時間の長さを考えれば,インデントの手間など微々たるものだ.だから,次のようにも言ってもよいだろう.
CENTER:&size(24){''初心者はエラーに悩む前にきちんとインデントすべし.''};
&br;

* プログラムの機能を部品化する [#m26f1641]

さて,今回はプログラムの機能を部品として扱う重要な話をしよう.
これができないと,プログラムはたちまちだらだらと長くてわかりにくいものとなり,扱い難いリヴァイアサンと化してしまうだろう.

** 機能単位として関数を定義: def [#m760e881]

一定の機能を果たす小さなプログラム部分を,関数として定義する方法を学ぼう.
文法は簡単で,
&br;
&size(24){''def'' &color(blue){関数名}; ''('' &color(blue){入力変数名1, 入力変数名2, ...}; '')''};
    &size(24){&color(blue){この関数の動作, 計算など};};
    &size(24){''return'' &color(blue){出力値};};
&size(24){''end''};
&br;
となる.
青字の部分が自分で変えられる部分だと思えば良い.
&ref(/materials/warning.png); 関数の「入力変数名」はその関数定義の中で「のみ」有効だ.数学っぽいね.
&br;

&ref(/materials/notes.png); 半径と長さを与えるとその円柱の体積を出力するプログラムを書くことを考えよう.
このとき,この ''def'' を使って,
+ 底面積を関数にする
+ さらに体積も関数にする

ようにしてみよう.
&br;&br;

まず,わかりやすく全体構造を先に考えると,少なくとも
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# 半径をもらって円の面積を出力する関数.
def circle(radius)
  計算…
  return 円の面積
end

# 半径と高さをもらって円柱の体積を出力する関数.
def volume(radius,length) 
  とりあえず底面積は上の circle 関数を使って circle(radius) で求まるとして
  円柱の体積を求めたい.
  return 求めた円柱の体積
end

# 以下,プログラム本体.
r = ARGV[0].to_f
l = ARGV[1].to_f

v = volume(r,l)  # volume という関数を自分で上の方で作ったという前提で

print("Volume is ",v,"\n")
}}
という感じになるだろう.
&ref(/materials/warning.png); このように,「先に全体構造をプログラムしてしまう」と脳みそが楽をできる.だから,
CENTER:&size(24){''このように,細かいところは後回しにして,''};
CENTER:&size(24){''先にざっと全体構造を書く癖をつけるとよい.''};
&br;

&ref(/materials/warning.png); 上のプログラムでは ''#'' を使ってプログラムを読む人間用にコメントを書いている.これは,
CENTER:&size(24){''Ruby はプログラム中の ''#'' 以降 行末までを無視する''};

ことを利用している. この「コメント」はプログラムを書くとき自分の助けになるので,積極的に利用しよう.
&br;

次に,「円柱の体積 = 底面積 × 高さ」であるので,これを volume 関数にプログラムすると以下のようになるだろう(該当部分だけ抜粋).
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
def volume(radius,length) 
  return circle(radius)*length
end
}}
&br;

そして,最後に残る底面積関数 circle だが,「円の面積 = π * (半径の二乗)」なので,これをプログラミングして(該当部分だけ抜粋),
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
def circle(radius)
  return PI * (radius ** 2)
end
}}
という感じになるだろう.
&br;

さて,これでプログラム全体が完成したので,(プログラムファイル名が cylinder.rb だとして)例えば
  ruby -w cylinder.rb 1.2 1.1

とすると
> Volume is 4.97628276328623

という出力が返ってくるはずだ.
&br;

** def のありがたみがピンと来ないんだけど? : 素数判定プログラムを書き直してみよう [#cb00723b]

''def'' のありがたみはプログラムの機能や動きがわかりやすくなるところにあるのだが,上の例だと単純すぎて,''def'' のありがたみがわからないかもしれない(センスの有る人はわかるだろうけれども).
&ref(/materials/notes.png); そこで,前回の総仕上げである「2 から与えられた数までの整数について,すべて素数かどうか判定して出力せよ」という問題のプログラミングをこの ''def'' を使ってやり直してみよう.

まず,このプログラムに必要な機能のようなものを考えて丁寧にリストアップしてみると,

+ ある数 ''n'' がある数 ''i'' で割り切れるかどうかを知る関数
+ (上の機能を繰り返し使って)ある数 ''n'' が素数かどうか判定する関数

があれば良さそうなことは分かるとおもう.
そこで,これらを関数にすることを前提として,全体を考えてみると次のような感じになるだろう.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# n と i をもらって,n が i で割り切れるかどうか出力する関数.
def divisible(n,i)
  n と i をもらって,n が i で割り切れるかどうかを判定する.
  return n が i で割り切れるなら yes, そうでなければ no という感じの返事を出力.
end

# n をもらって,n が素数かどうかを判定して出力する関数.
def is_prime(n)
  n をもらって,n が素数かどうかを判定する.
  return n が素数なら yes, そうでなければ no という感じの返事を出力.
end

# 以下,プログラム本体.
last_n = ARGV[0].to_i

for n in 2..last_n do
  if is_prime(n) then
      print(n," is a prime number.\n")
    else
      print(n," is not a prime number.\n")
  end
end
}}
&ref(/materials/warning.png); このプログラムの本体部分(15行目から21行目)をよく見よう. とてもわかりやすくなっていることに気づくだろう. これが関数を用いる利点の一つである(物事の複雑さを階層化できる).
&br;

さて,このままだともちろん動かないので,必要な関数をきちんとプログラムしよう.
まず,「n が素数かどうかを判定する」プログラムの以前のバージョンは(上にもあるが)
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
(n が与えられているとして)
isPrime = " is a prime number."
upper = sqrt(n).floor
for i in 2..upper do
  if (n % i == 0) then
    isPrime = " is not a prime number."
  end
end 
print(n,isPrime,"\n")
}}
であったので,これを上の ''divisible'' を使う前提で ''is_prime'' 関数の文法にマッチするように直すと次のような感じになる(わかりやすいようにコメントをいれてある).
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# n をもらって,n が素数かどうかを判定して出力する関数.
def is_prime(n)

  # まず,n は素数だと「答え」を仮定しておく.
  answer = true

  # 割り切れるか調べる範囲は √n まででいい.
  upper = sqrt(n).floor

  # 2 から √n まで i を変えて調べる.
  for i in 2..upper do
    # n が i で割り切れるなら,「n は素数ではない」と答えを変更する.
    if divisible(n,i) then
      answer = false
    end
  end 

  # 答えを関数値として出力する.
  return answer
end
}}
&ref(/materials/warning.png); プログラム中の ''true'', ''false'' は特別な意味があって,それぞれ論理値の「真」「偽」を表す.
この真偽値は ''if'' 文の判定部分の入力値として扱える.
まあ,
CENTER:&size(24){''Ruby では真は true で,偽は false で表す''};

と覚えておけば良い.

さて,これがわかれば ''divisible'' 関数も簡単に作れる.
これまでの文法に沿って馬鹿正直に作るのならば,例えば以下のようになるだろう.
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# n と i をもらって,n が i で割り切れるかどうか出力する関数.
def divisible(n,i)
  if (n % i == 0) then
      return true
    else
      return false
  end
end
}}
かっこよく書くならば? 次のような感じになるだろう.
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# n と i をもらって,n が i で割り切れるかどうか出力する関数.
def divisible(n,i)
  return (n % i == 0)
end
}}
&ref(/materials/warning.png); なぜこう書けるか,考えてみよう.
&br;

これでプログラムは完成した.念のために全体を載せておこう.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# n と i をもらって,n が i で割り切れるかどうか出力する関数.
def divisible(n,i)
  if (n % i == 0) then
      return true
    else
      return false
  end
end

# n をもらって,n が素数かどうかを判定して出力する関数.
def is_prime(n)

  # まず,n は素数だと「答え」を仮定しておく.
  answer = true

  # 割り切れるか調べる範囲は √n まででいい.
  upper = sqrt(n).floor

  # 2 から √n まで i を変えて調べる.
  for i in 2..upper do
    # n が i で割り切れるなら,「n は素数ではない」と答えを変更する.
    if divisible(n,i) then
      answer = false
    end
  end 

  # 答えを関数値として出力する.
  return answer
end

# 以下,プログラム本体.
last_n = ARGV[0].to_i

for n in 2..last_n do
  if is_prime(n) then
      print(n," is a prime number.\n")
    else
      print(n," is not a prime number.\n")
  end
end
}}
&ref(/materials/warning.png); いろいろ丁寧に書きすぎたので長くなっているが,本質としてはわかりやすくなっている.そこに着目しよう. これは「階層化して複雑さを分割することで,一度に全部を考えなくて良い」ことによる. 人間の脳みそは一度に多くの複雑さを相手にできないことを考えると,この「複雑さの分割」をしないとプログラミングはまっとうにできないということになる. つまり,
CENTER:&size(24){''def を制するものはプログラミングを制する(誇張)''};

のである.''def'' を使ってプログラムする癖を身につけよう.
&br;

** さらなる例題:コイントス [#e00b01ba]

''def'' の使いこなしは重要なので,他の例題もやってみよう.
&ref(/materials/notes.png); 指定した回数だけコイントスをおこなわせてコインの表と裏が何回出たか出力するプログラムを書こう.

コイントスをどうするかの具体的なことは後で詰めるとして,例によって全体をプログラミングしてしまおう.例えば次のような感じになるだろう.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# コイントスを一回行って,表かどうかを出力する関数.
def is_toss_front 
  コイントスを行って,表が出るか裏が出るか調べる.
  return 表が出たら true, そうでなかったら false を出力.
end

# 以下,プログラム本体.
toss_n = ARGV[0].to_i

# 表が出た回数.最初はゼロ.
front_n = 0

# toss_n 回,コイントスを繰り返す.
for n in 1..toss_n do
  if is_toss_front then
    # 表が出たら,カウントを 1 増やす.
    front_n += 1
  end
end

# 最終結果を出力しよう.
print("front: ", front_n, " back: ", toss_n - front_n, "\n")
}}
&br;

コイントスには,Ruby のもつ乱数関数 ''rand'' を利用しよう.
これは出力が 0 以上 1 未満の乱数関数なので,
「''rand'' が 0以上 0.5未満ならばコインの表が,0.5以上 1未満ならば裏が出たと解釈すれば」
通常のコイントスをしたことに相当するはずだ.
つまり,次のような感じになる(該当部分のみ).
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# コイントスを一回行って,表かどうかを出力する関数.
def is_toss_front 
  if (rand < 0.5) then
      return true
    else
      return false
  end
end
}}
カッコよく書くならば? 次のように書いても良い.
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# コイントスを一回行って,表かどうかを出力する関数.
def is_toss_front 
  return (rand < 0.5)
end
}}
&ref(/materials/warning.png); ''rand'' は,''rand(n)'' として正整数の引数を与えると 0 以上 ''n'' 未満の整数を乱数として出力する.これを利用しても良い.余力のある人はこちらのやりかたに書き直してみよう.
&br;

さて,これでプログラム全体が完成したので,(プログラムファイル名が coin.rb だとして)例えば
  ruby -w coin.rb 50

とすると
> front: 26 back: 24

などという出力が返ってくるはずだ.ここまでやってみよう.
&br;

* 今日の総仕上げ [#oca8dc38]
酔っぱらいのおっさんが,歩く様子を簡単にシミュレートして出力するプログラムを作ろう.具体的には,
酔っぱらいのおっさんが歩く様子を簡単にシミュレートして出力するプログラムを作ろう.具体的には,
+ 一歩進むとき,まっすぐ前に進む確率は 40%
+ 一歩進むとき,斜め右か左に進む確率はそれぞれ 30%
+ 幅30歩分ぐらいの広い道路の真中から歩き始めた
+ (上の幅から逆算して)まあ全部で30歩分ぐらいシミュレートする
+ 一歩ごとに,どこにいるかを ''print'' 文でそれっぽく? 出力

というのが条件だ.
例えば,(プログラムファイル名が walk.rb だとして)
  ruby -w walk.rb

とすると
> ----------------o--------------
> ----------------o--------------
> ---------------o---------------
> --------------o----------------
> -------------o-----------------
> -------------o-----------------
> -------------o-----------------
> --------------o----------------
> --------------o----------------
> --------------o----------------
> ---------------o---------------
> ---------------o---------------
> ---------------o---------------
> --------------o----------------
> -------------o-----------------
> -------------o-----------------
> ------------o------------------
> ------------o------------------
> ------------o------------------
> ------------o------------------
> ------------o------------------
> ------------o------------------
> ------------o------------------
> -----------o-------------------
> ----------o--------------------
> ----------o--------------------
> ----------o--------------------
> ---------o---------------------
> ---------o---------------------
> ---------o---------------------

のような出力が返ってくるようにということである(確率の話なのでプログラムを実行する度に結果は違う).
この例だと,"o" がおっさんのいる場所を表していて,上から下へ時間が経っているということになる.
&br;

&ref(/materials/warning.png); おっさんが一歩で前か斜め右か斜め左かのどこへ進むかはコイントスと似たような関数を作れば良い.
&ref(/materials/warning.png); 場合分けが前と右と左の 3通りになるので,そこはどうする?
&ref(/materials/warning.png); おっさんの場所を ''print'' する関数も作るとわかりやすい.
&ref(/materials/warning.png); 例によってプログラム全体を先に設計して書いてしまおう.

* レポート [#pb33cc5c]

本日受けた講義および行った実習について,簡単にまとめた報告をせよ.
また,総仕上げの部分で自分で書いたプログラムを報告せよ.

もちろん各自の

+ 所属(学部,学科)
+ 学籍番号
+ 学年
+ 氏名
+ 日時
+ 肝心のレポート内容(得た知見,作業について気づいたこと等も)

を書くのを忘れないように. 

* about Icons, ClipArts [#jfbc664c]
Some icons in this page are downloadable at [[ICONFINDER:http://www.iconfinder.net/]].

The "note" icon &ref(/materials/notes.png); designed by [[Marco Martin:http://www.notmart.org/]] is distributed with the LGPL licence,
the "warning" icon &ref(/materials/warning.png); designed by [[Alexandre Moore:http://nuovext.pwsp.net/]] with the GPL licence
and the "triangle" icon &ref(/materials/JNorth_arrow-right-sm.png); designed by [[Joseph North:http://sweetie.sublink.ca/]] is distributed with the [[Creative Commons (Attribution-Noncommercial-Share Alike 3.0 Unported):http://creativecommons.org/licenses/by-nc-sa/3.0/]] licence.

Some clip arts used in this page are downloadable at [[Open Clip Art Library:http://www.openclipart.org/]].
We deeply appreciate their superb works. With licence, they describe that "the actual clipart content on open clipart library is Public domain" in the web.

// ━┃┏┓┛┗┣┳┫┻╋


// コマンドライン入力は「行頭をブランクで始める」.
// コマンドライン出力は「行頭を > で始める」.

// 実習アイコン
// &ref(/materials/notes.png); 

// 注意アイコン
// &ref(/materials/warning.png);

// Link アイコン
// &ref(/materials/JNorth_arrow-right-sm.png);

// 大文字での強調 
// CENTER:&size(24){''ほげほげ''};

// programu source 表記
// #highlighter(language=ruby,number=on,cache=on){{}}