授業資料/06 の変更点


#contents

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

というのが条件だった.さて,これについて実際にやってみよう.

プログラムの完成形をみるのではなく,どのように作っていくか解説しよう.
ただし,この場合重要な「コツ」があるので,それを先に書いておこう.
&br;

CENTER:&size(24){''プログラムは最初は小さく書く.''};
CENTER:&size(24){''そして,動く状態をほぼ常にキープしつつ,少しずつ機能を追加する.''};
&br;

このやりかた(アジャイルソフトウェア開発もどき)は現在のソフトウェア工学でプログラム開発手法として重要視されている方法論の一つで,以下のような利点がある(もちろん欠点もある.「計画性が無い」のが最大の欠点と言われる).
+ 段階的にプログラムするので,対処すべき複雑さがあまり大きくなくて良い.
+ 問題点にすぐ気づきやすく,対処可能.
+ プログラムが小さいうちからエラーが常に排除されるので,エラーに悩まされない.
+ どの段階でもプログラムは「動く」ので,安心である(初心者にとっては特に心理的によろしい).

&ref(/materials/warning.png); 実際に,以下に示すプログラム開発の段階で示した例すべてが「エラー無しで動く」ことに着目しよう.

** 全体構造を考える [#xf1b69bd]
まずは全体構造を作るべしということで,上の条件をみたすプログラムの全体構造を考えてみよう.
とりあえず,30歩進む,その一歩ごとに方向がランダムに決まって,その様子を出力ということなので,まずは
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# 30歩進む
for step in 1..30 do
  # 進む方向を乱数で決定
  # 画面に出力
end
}}
こうなる.
これから,進む方向を決める関数と,画面に状態を出力する関数の二つを作ると良さそうだということがわかるので,関数の名前を決めて次のように改良する.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# 進む方向を乱数で決定
def man_direction
end

# 画面に出力
def man_print
end

# 以下,プログラム本体.

# 30歩進む
for step in 1..30 do
  # 進む方向を乱数で決定: man_direction
  # 画面に出力: man_print
end
}}

** 機能を増やしていく: 本体をきちんと [#e6a17c41]

さて,問題からして「おっさんが道のどの辺りにいるのか」を記憶しておく変数が必要なので,
それを変数 ''man_location'' (整数値)として,例えば
+ 左端をゼロとして,右方向にプラスで数字が増える
+ だから,真ん中は 15 ぐらい?

とでも設定しよう(真ん中をゼロとしても良いし,いろいろバリエーションが考えられるね).

そして,こうすると,関数 ''man_direction'' の持つべき性質も決まる.右に進むとプラス 1 と設定したのだから,

| おっさんの行動 | ''man_direction'' の出力値 |h
| 斜め前右へ | +1 |
| まっすぐ前へ | 0 |
| 斜め前左へ | -1 |

として,これを一歩ごとに ''man_location'' に足せば良さそうだ.
あと,おっさんの位置さえわかれば画面出力もできるはずだから,''man_print'' が ''man_location'' を引数に持つことも確定だ.

この設定を反映させるとプログラムは次のように改良される.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# 進む方向を乱数で決定
def man_direction
  # 乱数が「右」を選択したら 
  # return 1 
  # 乱数が「まっすぐ」を選択したら 
  # return 0 
  # 乱数が「左」を選択したら 
  # return -1 

  # 仮に「まっすぐ」を出力しておく
  return 0
end

# 画面に出力
def man_print(loc)
end

# 以下,プログラム本体.

# おっさんの位置. 最初にいる場所は真ん中.
man_location = 15

# 30歩進む
for step in 1..30 do
  # おっさんの位置は man_direction の結果が「足される」.
  man_location += man_direction

  # 画面に出力: man_print
  man_print(man_location)
end
}}
さて,この状態でどうプログラムが動いているかなにか見てみたいので,''man_print'' を簡単に実装しよう.
別に絵っぽいものを今の時点で出力できなくてもよいのだから,ごく簡単に
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# 進む方向を乱数で決定
def man_direction
  # 乱数が「右」を選択したら 
  # return 1 
  # 乱数が「まっすぐ」を選択したら 
  # return 0 
  # 乱数が「左」を選択したら 
  # return -1 

  # 仮に「まっすぐ」を出力しておく
  return 0
end


# 画面に出力
def man_print(loc)
  print(loc, "\n")
end

# 以下,プログラム本体.

# おっさんの位置. 最初にいる場所は真ん中.
man_location = 15

# 30歩進む
for step in 1..30 do
  # おっさんの位置は man_direction の結果が「足される」.
  man_location += man_direction

  # 画面に出力: man_print
  man_print(man_location)
end
}}
としてみよう.
&ref(/materials/warning.png); この状態でプログラムを動かしてみると,''15'' が30回出力されるはずである(うまく動いていて,一安心だ).

** 機能を増やしていく: おっさんが次に進む方向を決める機能 [#l852ec37]

さて,おっさんの次の一歩を乱数で決める関数 ''man_direction'' に取り掛かろう.
全確率 1 を三通りにわけるわけで,

CENTER:&ref(./probability-sm.png);
CENTER:確率分配の様子

となっていることから考えて,次のように場合分けすることになる(該当部分のみ).
ただし,3通りの場合分けは今の学生の知識だと ''if'' を二段重ねることになる.
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# 進む方向を乱数で決定
def man_direction
  r = rand # サイコロを振るのは一歩に一回だけ.結果を取っておく.

  if (r < 0.3) then # 乱数が「左」を選択したら 
      return -1
    else 
      if (r < 0.7) then # 乱数が「まっすぐ」を選択したら 
          return 0
        else # 残るは「右」
          return 1 
      end
  end
end
}}
&ref(/materials/warning.png); Ruby ではこのようなときに ''else if'' を縮めて ''elsif'' と書くこともできる(この場合,全部で一文なので ''end'' は一つ減る)ので,そうすると以下のようになる.動作は本質的には同じなので,好きな方を選べばよいが,''elsif'' の方がおそらく読みやすく,トラブルも少ないだろう.
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# 進む方向を乱数で決定
def man_direction
  r = rand # サイコロを振るのは一歩に一回だけ. 結果を取っておく.

  if (r < 0.3) then # 乱数が「左」を選択したら 
      return -1
    elsif (r < 0.7) then # 乱数が「まっすぐ」を選択したら 
      return 0
    else # 残るは「右」
      return 1 
  end
end
}}
&br;

さて,非常に良く見られる''「間違い」''について注意しておこう.
&ref(/materials/NG.png); この時,判定のすべての部分に ''rand'' 関数を使って以下のようにしてはいけない!!!  
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# 進む方向を乱数で決定
def man_direction
  if (rand < 0.3) then # 乱数が「左」を選択したら 
      return -1
    elsif (rand < 0.7) then # 乱数が「まっすぐ」を選択したら 
      return 0
    else # 残るは「右」
      return 1 
  end
end
}}
なぜなら,こうすると確率の配分ぐあいが変わってしまい,この場合だと
''左: 30%, まっすぐ: 49%, 右: 21%''
になってしまい,問題と異なる現象になってしまうからである(一回で済むはずの ''rand'' を数回行うのが無駄ということもあるけれども).

&ref(/materials/warning.png); なぜこうなってしまうかよく考えよ.
CENTER:&size(24){''この種の間違いは動作を見ても気づきにくい.''};
CENTER:&size(24){''プログラムを書くときによく考えて気づけ!!''};
&br;

さて,この段階でプログラムを実行すると最初は ''15'' で,あとは少しずつ変わっていく数字が出てくるだろう.
つまり,プログラムの計算部分はもう完成したということで,あとは出力の見かけの問題に過ぎない.

** 機能を増やしていく: おっさんの位置を「絵っぽく」出力する機能 [#cd196bd8]

あとは ''man_print'' の機能を実装すれば良い.
発想としては,例えば ''man_location'' が ''17'' の時に
  ----------------o-------------
という出力を出したければ,
+ まず 16個の "-" を ''print'' し,
+ 続けて 1個の "o" を ''print'' し,
+ さらに 30-17=13個の "-" を ''print'' し,
+ 最後に改行を ''print'' 

すればよい.
だから,例えばこれを素直にプログラミングして,
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# 画面に出力
def man_print(loc)

  for i in 1..(loc-1) do
    print("-")
  end

  print("o")

  for i in 1..(30-loc) do
    print("-")
  end

  print("\n")

end
}}
とすればよいはずだ.これでプログラムは完成である!! 動かしてみよう.望む結果が得られるはずだ.

&ref(/materials/warning.png); Ruby には決められた回数だけ単純に繰り返すための文法 ''回数.times {}'' が特別にあって,上のプログラムは以下のようにも書ける.こちらの方が好きだという上級者はこちらでもよいだろう.
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
# 画面に出力
def man_print(loc)

  (loc-1).times {
    print("-")
  }

  print("o")

  (30-loc).times {
    print("-")
  }

  print("\n")

end
}}

* 集合を扱う : 配列 [#d66c65f6]

さて,これまで扱ってきた変数は整数だったり実数だったり文字列だったり真偽値だったりしたが,そうしたものの「集合」は扱ってこなかった. しかし,集合が扱えるとより便利だ.
そこで,ここで集合をプログラムでどう扱うかについて学ぼう.
今回は,プログラミング言語で扱えるもっとも単純かつ基礎的な集合である「配列」について学ぼう.

配列とは,単に要素を集めて集合としたものである.Ruby では,
&br;
CENTER:&size(24){''配列は [ ] の中に要素を ,(カンマ) で区切って並べるだけ ''};
&br;

で配列になる.
&ref(/materials/warning.png); Ruby では配列の要素の「種類」は同じでなくて良い.整数でも実数でも文字列でも真偽値でも混在できる.
&br;

そして,配列について様々な操作ができる.それを表にしてみると以下のようになる.

| 操作 | 説明 | 例 |h
| 配列を作る | 変数名 = 配列 でよい | &color(blue){a = [1,5,2,-8]}; |
| 要素がいくつ入ってる? | .size をつければよい | &color(blue){a.size}; とすると上の例だと 4 になる |
| i番目の要素 | [i-1] を変数名につける | &color(blue){a[3]}; とすると上の例だと -8 になる |
| 要素を追加 | unshift (先頭に追加の場合) | &color(blue){a.unshift(6)}; とすると上の例だと a = [6,1,5,2,-8] になる|
|~| push (末尾に追加の場合) | &color(blue){a.push(6)}; とすると上の例だと a = [1,5,2,-8,6] になる|
| 要素を削除 | shift (先頭要素を削除の場合) | &color(blue){a.shift}; とすると上の例だと a = [5,2,-8] になる |
|~| pop (末尾要素を削除の場合) | &color(blue){a.pop}; とすると上の例だと a = [1,5,2] になる |
||||
| (集合間の操作) || &color(blue){a = [1,5,2,-8], b = [3,1,-1,10,5]}; として |
| 共通集合 | 集合a &amp; 集合b とする | &color(blue){a &amp; b}; は [1,5] になる |
| 和集合 | 集合a &brvbar; 集合b とする | &color(blue){a &brvbar; b}; は [1, 5, 2, -8, 3, -1, 10] になる |
| 差集合 | 集合a - 集合b とする | &color(blue){a - b}; は [2, -8] になる |
| 集合の単純結合 | 集合a + 集合b とする | &color(blue){a + b}; は [1, 5, 2, -8, 3, 1, -1, 10, 5] になる |

さて,少しずつ慣れていこう.
まず,昔々みたはずの,5つの要素 1.2, 3.0, 3.3, 4.1, 0.9 をもつ配列を作って,それに対して操作する形でこの5つの数字の相加平均を計算するプログラムを見てみよう.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

a = [1.2, 3.0, 3.3, 4.1, 0.9]

sum = 0.0
for i in 1..(a.size) do
  sum += a[i-1]
end

average = sum/a.size

print(average,"\n")
}}
どうだろうか.文法的に難しいところも特に無いはずだし,全体も簡単だ.

&ref(/materials/warning.png); [上級者向け] 集合の要素全部に対してなにか操作を行うという機会は意外に多いが,それを ''for'' 文で行うと冗長だ.
例えば,上のプログラムでは配列 ''a'' に対してその操作を
&size(18){ ''for i in 1..(a.size) do''};
&size(18){  ''a[i-1]''  に対して操作};
&size(18){ ''end''};
としているが,''1..(a.size)'' というのは情報的に不要であるなど,この書き方には無駄が多い.そこで,次のようなすっきりした書き方(iterator)が Ruby には用意されている(ここ最近のプログラミング言語では iterator が充実しており,とても便利である).

&size(24){''&color(blue){集合名};.each { |&color(blue){要素名};| ''};
&size(20){  ''&color(blue){要素名に対して操作(要素名 が a[i-1] の代わりになる)};'' };
&size(24){''}''};
# 青い字のところは自分で変える部分だ.

この書き方の意味と利点がわかるならば,どんどん使うとよいだろう.
ちなみにこれを使って上のプログラムの該当する ''for'' 文を書き換えると
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
a.each { |e|
  sum += e
}
}}
などとなる. 少なくとも「すっきり書ける」ことは見た目ではっきりわかるだろう.
&br;
&ref(/materials/notes.png); さて,5つの要素 1.2, 3.0, 3.3, 4.1, 0.9 をもつ配列を作って,それに対して操作する形でこの5つの数字の「幾何平均」を計算しよう.
&ref(/materials/warning.png); a_1, a_2, a_3, ... a_n という n 個の数字の幾何平均とは,この n 個の数字の積をとり, (1/n)乗したものである. つまり,(a_1 a_2 a_3 ... a_n)^(1/n) のこと.
&br;
さて,プログラムは全体構造として次のようになるはずだ.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

a = [1.2, 3.0, 3.3, 4.1, 0.9]

# さて,どうやって幾何平均を求めるかね.
# 相加平均についてはやったので,それを参考に…

print(average,"\n")
}}
肝心の幾何平均を求める部分は自分で考えてみよう.
そして,このプログラムを実行すると,''2.12994584474845'' が得られるはずである.
&ref(/materials/warning.png); 答えが ''1.0'' になってしまう人は何が悪いか考えよう.以前にも似たようなことがあったよね?
&br;

&ref(/materials/notes.png); 配列をベクトルととらえて,二つのベクトルのユークリッド内積を計算する関数を作ってみよう.
// programu source 表記
#highlighter(language=ruby,number=on,cache=on){{
include Math

# 内積.ベクトルの次元数は同じものと仮定している.
def inner_product(va,vb)

  sum = 0.0
  for i in 1..(va.size) do
    sum += va[i-1] * vb[i-1]
  end

  return sum
end

# 以下,プログラム本体.
a = [1.2, 3.0, 3.3]
b = [2.1, -1.5, 0.2]

print(inner_product(a,b),"\n");
}}

&ref(/materials/notes.png); さらにこれを発展させて,(ユークリッド的な)ベクトルの長さや二つのベクトル間の角度を計算する関数を作ってみよう. そして,上の例の二つのベクトルの長さ,間の角度を求めよ.
上の場合,それぞれ ''a'' の長さは ''4.618...'', ''b'' の長さは ''2.588...'', 間の角度は ''1.681...''(rad) となるはずだ.

&br;
&ref(/materials/warning.png); 配列は入れ子にできる.これは多次元配列として使える重要なものなので強調しておこう.
CENTER:&size(24){''配列は入れ子にできる(大事なことなので二回言いました)''};
&br;

これは重要な話なので少し丁寧に書こう.例えば,
// programu source 表記
#highlighter(language=ruby,number=off,cache=on){{
a = [[1,2,3],[4,5,6],[7,8,9]]
}}
と入れ子にした配列 ''a'' を定義したとしよう.
これはちょうど行列
|1|2|3|
|4|5|6|
|7|8|9|
を ''a'' と定義したことに相当して,それぞれの要素には
| a[0][0] | a[0][1] | a[0][2] |
| a[1][0] | a[1][1] | a[1][2] |
| a[2][0] | a[2][1] | a[2][2] |
でアクセスできる事になる(全部を転置して考えてもいいけどね).
&ref(/materials/warning.png); 添字が数学のそれより 1 ずつ小さいのを忘れると間違える.要注意だ.
これを使えば,二次元データなどの多次元データを配列で扱える.重要な応用なので覚えておこう.

&ref(/materials/warning.png); Ruby で決められたサイズの配列を「作る」のには苦労するかもしれないので,教えておこう.
&size(24){''例えば n 行 m 列型の配列 a を作るには,''};
CENTER:&size(24){''a = Array.new( &color(blue){n}; ){ Array.new( &color(blue){m}; ){ &color(blue){0}; }}''};

&size(24){''とすればよい''};
ただし,この場合中身は全部 ''0'' になる.必要に応じて各成分を書き換えよう.
ただし,この場合中身は全部 ''0'' になる.必要に応じて, こうやって配列を作ってから各成分を書き換えよう.
もちろん,''for'' 文と ''push'' を使ってこれに相当する関数を作っても良い.
&br;

* 今日の総仕上げ [#zcf67201]

行列とベクトルの積,行列と行列の積を計算する関数をプログラミングしよう.
具体的には以下のようなものとする.

+ 行列は正方行列のみ扱うとしておく
+ 次元の違いのチェックはしなくてもよい(もちろんしてもよい)
+ (上級者向け)上で定義した ''inner_product'' 関数を用いて実装する

そして,4 x 4 の行列を適当に二つ,4次元ベクトルを適当に一つ用意して,それらに対して積の計算がうまくいくことを確認せよ.
ちなみに「うまくいく」とは,手計算と答えが一致することを示すことを指すものとする.

* レポート [#cfc0ba89]

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

もちろん各自の

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

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

* about Icons, ClipArts [#vcf50e81]
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){''ほげほげ''};

// OK アイコン
// &ref(/materials/OK.png); 

// NG アイコン
// &ref(/materials/NG.png); 

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