脱メタボリック計画

前期に 腹周りを気にしていたサラリーマン は、健康診断にひっかかったことをきっかけに減量に取り組むことにした。 毎朝 5 km 走って減量するプログラムが組まれた。 理想体重は現在の体重の 80 % であるということだった。 最初の代謝は 1 km 走ると 0.3 [kg] しか減らない。 def_metabolic.rb


def running(l, w)
decrease = - 0.3 w += l * decrease
end weight = 100 length = 5 ideal = 100 * 0.8 # 理想体重 printf("初期体重 %d [kg], 走行距離 %d [km]\n", weight, length) j = 0 while weight > ideal
weight = running(length, weight) printf("%3d ヶ月目 体重 %4.1f [kg]\n", j += 1, weight)
end

実行してみる。

初期体重 100 [kg], 走行距離 5 [km]  
  1 ヶ月目 体重 98.5 [kg]
  2 ヶ月目 体重 97.0 [kg]
  3 ヶ月目 体重 95.5 [kg]
  4 ヶ月目 体重 94.0 [kg]
  5 ヶ月目 体重 92.5 [kg]
  6 ヶ月目 体重 91.0 [kg]
  7 ヶ月目 体重 89.5 [kg]
  8 ヶ月目 体重 88.0 [kg]
  9 ヶ月目 体重 86.5 [kg]
 10 ヶ月目 体重 85.0 [kg]
 11 ヶ月目 体重 83.5 [kg]
 12 ヶ月目 体重 82.0 [kg]

なぜ数字が揃っているのか説明せよ。

体力があがったら

このサラリーマンの標準体重は 80 [kg] であったとしよう。 標準代謝に近づくと同じだけ走っても筋肉も増え始めるので体重は減らなくなる。 85 kg より減ったあとは、1 [km] 走っても 0.1 [kg]しか減量できなくなった。


def running(length, weight)
  if weight > 85 
      decrease -= 0.3  
  else decrease -= 0.1
  end
  :
end

標準体重に近づくにつれ減量しにくくなる結果を得るか?

このプログラムでは、running という method で、 実行部分からもらってきた length と weight に応じて、 返す値を変更している。length の値を 1 ずつ変更し、 1 年後に 80 kg 以下になるには 1 日どのくらい走るべきか調べよ。

計算される値について bc -l で確かめておこう。

自作の method

何度も使う部分はあらかじめ実行文の前に定義しておくと、 実行文の部分がすっきりと見やすくなる。これを method と呼んだ。


定義文の群
def  methodその1(仮引数1, 仮引数2, ...)
:
end

def  methodその2(仮引数1, 仮引数2, ...)
:
end 
   
:   

実行文の群

変数1, 変数2, ... の定義

method1(変数1, 変数2, ...)  methodその 1 を呼び出す

method2(変数1, 変数2, ...)  methodその 2 を呼び出す
:   

ほたるの数と米の収穫量

農薬によって、米の収穫量は上がるが、ほたるの数は減った。 稲作では農薬を何度も散布する。 ここでは、6 回の散布により、ほたるがいなくなくなるまでを、 調べてみることにする。 def_firefly.rb

実行してみる。

ほたるは 100 匹います。1 回目の農薬を散布します。
米の収穫量: 100 [kg]    ほたるの生息数 98 匹
2 回目の農薬を散布します。
米の収穫量: 200 [kg]    ほたるの生息数 94 匹
3 回目の農薬を散布します。
米の収穫量: 600 [kg]    ほたるの生息数 86 匹
4 回目の農薬を散布します。
米の収穫量: 2400 [kg]   ほたるの生息数 70 匹
5 回目の農薬を散布します。
米の収穫量: 12000 [kg]  ほたるの生息数 38 匹
6 回目の農薬を散布します。
米の収穫量: 72000 [kg]  ほたるの生息数 -26 匹
ほたるはいなくなりました。

ほたるの数は整数か、自然数か。

シュミレーションで考えること

シュミレーションでは、 得られた数と実際が合っているのかどうか、 確認しなくてはならない。

米の収穫量: 72000 [kg]  ほたるの生息数 -26 匹
ほたるはいなくなりました。

ほたるの数は自然数の値であるので、自然数を取らなくなったら、 ゼロにして、計算を止める、 という処置が必要である。

このプログラムでは、firefly という method で、 実行部分からもらってきた egg と chemical に応じて、 返す値を変更している。

p 文などで計算される値を打ち出し、 実際にそうなるかどうか、bc -l で確かめておこう。

数値が理屈に通らないようなもの、例えば負の数値が method で使われたら、 method で値を判定する必要があることがある。 蛍の数が負のときは 0 に変更するようにしよう。



def firefly(egg, chemical)
  egg -= 2 ** chemical  # 農薬を散布するとほたるの卵の数は減る 
  if egg < 0
    return 0 # 卵の数は負の数をとらない                            
  else
    return egg
  end
end   

最後に計算された数値が戻る。何も指定しなければ、計算した値がそのまま return するので、 return 文は必要ないが、場合分けをするため return 文 が必要となる。実行し、0 匹になることを確かめよう。

これもだめで、あれもだめ

直角三角形の長辺の長さを求める三平方の定理では、 三角形の辺の長さは負の値を取らない。 長辺以外の辺に負の値が入っていたら、止めなくてはならない。 このような場合、論理演算子を使う。 直角三角形 ABC の長辺の長さ a を、 残りの二辺 b, c から三平方の定理を用いて計算するプログラム hypotenuse.rb を作ろう。定義部分で、 残りの 2 辺が負であれば計算を止めるように設定したい。


  :
def pythagoras(x, y)
  if x < 0 || y < 0   # x が負または y が負の値を代入された場合
        return nil    # 計算を止めよ
   end
   sum = (x**2 + y**2) ** 0.5    # そうでなければ長辺を計算せよ
end
  :

簡単な論理演算子についてまとめておく。

#  #  条件 A || 条件 B              # 条件 A または条件 B
#  #  条件 A && 条件 B              # 条件 A かつ条件 B

return 文についてもまとめておく。

#  #  return 値           # 値を返す
#  #  return nil          # 計算を止めてしまう場合

三平方の定理を使って、 長辺を求めるプログラムを完成させてみよう。

階乗計算

階乗の計算、カードの並べ替え、ハノイの塔、 などは実は同じことの繰り返しである。 まずは階乗計算をプログラムにしたものを見てみよう。

「ある正の整数 n について、その階乗を求めよ。」という定義は、

n! = n * (n-1) * (n-2) * ... * 2 * 1

である。これの意味するところは「n から順番に一つ小さい数を掛けていって、 1 になるまで求めよ」である。もちろん while -- end や for -- end を使ってもよいが、method を用いることもできる。 def_factorial.rb



#!/usr/koeki/bin/ruby

def factorial(m)
  if m == 1
    1
  else
    m * factorial(m-1)
  end
end

n = 1
# n = 2
# n = 3
# n = gets.chomp.to_i

printf("%dの階乗は%dです\n", n, factorial(n))

ひとつずつ確かめよう

n = 1 の場合は 1 である。n = 2 の場合と n = 3 の場合を入れて確かめてみよう。 def -- end の中で、n > 1 ならば、

n かける factorial(n-1)

を実行する。n - 1 = 1 (n = 2 のとき) なので、 factorial(1) = 1 が入る。よって答えは 2 * 1 = 2 を得る。

n = 3 の場合は、 n - 1 = 2 (n = 3 のとき) なので、 factorial(2) をかければよい。 factorial(2) は、前の結果を使って 2 なので、 3 * 2 = 6 を得る。以下同様。

n = 1 の場合を理解したら、n = 2, n = 3 ... と入れ換える。最後に、 n = 1 に # をつけて、残りの # を外して、 入力して答えが得られるようにせよ。

コマンドライン入力で作りかえてみよ。

トランプの並べ替え

順序が決まっているものを並べかえる作業は、どう行っているのだろうか。 例えば左から順に 1 からはじめて、最後は King (13) が来るようにしよう。 トランプで 1 つの絵柄 1 組を用意しよう。よく切ったあと、一枚を取り出す。 その一枚よりカードが大きかったら右、小さかったら左、と並べることになる。 4 を取り出した場合

               4

次が 9 だったら

               49

次が 2 だったら

         
               24

となるだろう。その次のカードも、順次、 始めに引いた数より大きいか小さいかで山を 2 つ作る。 この作業の終了時には

               34Q

のように、両側に積み上がっているものである。

完成に近づけるため、とりあえず左側の方を取って、また一枚を置く。今の場合は 1 から 3 である。2 を取った場合を考える。 今と同じ操作を行う。

もし 1 を引いたら、

               124Q

とする。3 を引いた場合には 2 の右側にカードを置く。 右側の山も同様にして、全て順番通り並べ終ることを確かめよう。

トランプのカードでなくてもよい。不要な紙を 8 等分にして、 この試行を再現せよ。

並べかえが完成すると、全ての数字が並ぶ。

            1234  ... K

このような並べかえを quicksort と呼ぶ。

クィックソート

このアルゴリズム quick sort を使ったカードの入れ替えプログラムは次のようになる。 これも、method を何度も呼び出して使う。 def_quicksort.rb

構造は次のようになっている。

def quicksort(number)
  # 定義 1
  # 数字を並べ変える方法について
end

def readNumArray()
  # 定義 2
  # 入力した数値をどのように配列にくっつけるか
end

def printArray(a)
  # 定義 3
  # 入力した数字を表示する方法について
end

# 配列を用意

# 入力した数値を表示

# 入れ替えた数値を表示

配列の操作が使われている。動かし方は

% ./quicksort.rb
8
2
1
3
5
4
9
6
7
Ctrl-D
整列前:
[6, 2, 3, 5, 7, 9, 8, 1, 4]

整列後:
[1, 2, 3, 4, 5, 6, 7, 8, 9]

である。

とりあえず全部並べる

順序が決まっているものを並べかえる作業は、どう行っているのだろうか。 例えば左から順に 1 からはじめて、5 までを並べかえることにする。 よく切ったあと、全て並べてみる。左端のカードを選ぶ。

(*) 左から順に、右隣とくらべて、大きければ入れ替える。 左隣が自分より小さく、右隣が自分より大きくなるまで実行する。

終了したらまた左端のカードを選ぶ。(*) くりかえし。

左端の 4 を右隣の 5 とくらべる。この場合は順番どおり。

45321

5 を選ぶ。右隣の 3 は小さいので、入れ替える。

43521

5 を選ぶ。右隣の 2 は小さいので、入れ替える。

43251

次に 5 を選ぶ。右隣の 1 は小さいので、入れ替える。

43215

右隣はなくなったので、また左端へ戻る。左端の 4 について、右隣の 3 は 小さいので入れ替える。

34251

以下くりかえす。

トランプのカードでなくてもよい。不要な紙を 8 等分にして、 この試行を再現せよ。

並べかえが完成すると、全ての数字が並ぶ。

12345

このような並べかえを bubble sort と呼ぶ。ソースはこれ