なぞかけ

なぞかけとは、次の空欄 A, B, C に適当なものを入れて笑いを得る手法である。

A といえば B だ。そのこころ は、 C だから。

これらに対し、A, B, C を埋めて、解答を作る。 また、モンタージュ写真なども同じ要領である。

寅さんばかりでなく、 他のキャラクタを描写するときにも適宜入れ替えて、 記述することができる。

関数だってくりかえし

関数とは、変数 x に対して定義されており、 変数 x を代入すると値 f(x) を返す。

などが、この関数の例である。 なぞかけやモンタージュ写真作成と同じように書き下せる。

グラフを描くときは、 適当な変数 x を選び、 f(x) の値を求め、少しずつなぞって描いた。

数学でも変数(parameter) x のことを引数(argument) と呼ぶことがある。 関数値 f(x) を変化させる関数 f(x) によって、 xが変われば、 その関数値 f(x) もそれに伴って変わるためである。

x = t の値を入れる----> f(t)の値を受け取る

x に任意の値 t を選ぶと、それにより f(t) が決まる; t = 1 ならば、 y = f(1) を得る。

どんな関数がきてもだいじょうぶ

次のような 3 つの関数 f(x) を考えよう。


y = 3 * x 
y = 2 * x - 1 
y = - 2 * x + 1 

x の特別な値を a とする。 a = 1 のとき、それぞれの y はどのようにして得られるだろうか。


y = 3 * a = 3 * 1 = 3
y = 2 * a - 1 = 2 * 1 - 1 = 1
y = - 2 * a + 1 = - 2 * 1 + 1 = -1

y = 3 * x, y = 2 * x - 1, y = - 2 * x + 1f(x) の部分だけ変更するようにする雛型を method という。


def 関数(仮引数)
関数の定義
end

x のある値 a を選ばせるようにするには

def f(x)
     # f という関数(Function) を定義する
     f(x)に必要な動作  
     # 仮引数は x
end                     

a = gets.to_i # キーボードから入力された文字列を gets 
              # 整数にする to_i
printf("%d\n", f(a)) method により定義された関数に x = a を代入

とする。

f(x) = 3 * x, 2 * x - 1, - 2 * x + 1 のそれぞれについて a = 1 を代入し、それぞれ 3, 1, -1 を得るか自分の計算で確認せよ。

これを代入した答はなあに

このプログラムを def_func.rb とすると、


def f(x)
3 * x
end a = gets.chomp!.to_i printf("%d \n", f(a))

となる。実行してみよう。 計算機で a = 1 を代入するには、キーボードで 1 を代入し、リターンキーを押す。

% ./def_func.rb                                                 [˜/Ruby]

1(リターン)
3(が出現する)

def f(x) -- end の中身を 3 * x, 2 * x - 1, - 2 * x + 1 と変更して、3, 1, -1 を得るか確認せよ。

print文をいったんコメントアウトして おなじプログラムを実行してみよう。何か気づいたことはないだろうか?

分かりやすいプログラムへ

このプログラムではユーザに不親切である。その理由は:

からである。では printf を使ってメッセージを出すようにして、 これら不親切な点を改良しよう。 まずはプログラムが開始したことが分かるようにしよう。

このプログラムの開始はどのような行動から始まるのか?

ユーザーに入力させることから始めるので、入力を促す文を Kterm に出力する。

STDERR 対話的な表示に使う「標準エラー(STanDard ERRor)出力」

プログラムが開始する部分は method の終った直後なので、

  

:
STDERR.print("x の値を決めて下さい: ")
a = gets.chomp.to_i 
printf("%d \n", f(a))

何をするのか始めに説明する文章をつけよう。


STDERR.print("与えられた数 x について計算をします \n")
STDERR.print("x の値を決めて下さい: ")

また、定義する関数の説明もしたい。 def -- end の中のみを変更するので、 ここに print 文を狭んで、説明することにする。

def f(x)
STDERR.print("入力した値を 3 倍します \n")
3 * x
end

この部分の改良により、プログラムの開始時点で、 ユーザは入力した値を 3 倍した値が得られることが分かる。 ついでに、答える部分も分かりやすくしよう。


printf("計算した結果は %d です \n", f(a))

実行してみよう。ユーザに分かりやすいプログラムづくりも重要なポイントである。

STDERR 対話的な表示に使う標準エラー(STanDard ERRor)出力

定義部分の method を用意したあと (def -- end)、 実行文 STDERR.print を始める。 method で使う用語をまとめておく。

仮引数(かりひきすう) 関数の中で使う変数のこと
関数 仮引数を使って作られているもの
引数(ひきすう) 実行文の中で受け取る変数のこと

描画ソフトの真似

表計算ソフトなどでグラフを描かせることができる。 グラフ機能であらかじめ作っておく部分は、 グラフの種類(棒グラフや折線グラフ)である。 機能に必要な部分は予め仮引数で作成しておく。 ユーザはグラフ機能を呼び出して、描画させる。

グラフ機能に表計算ソフト変数 x に値を代入し、 グラフの形状 (barplot(x)lineplot(x)) で、 値を可視化する作業を実行する機能をもたせておき、 ユーザが計算させたい値を代入(入力)し、グラフを得ているのである。

プログラムの構造

グラフ機能を作っておいて、ユーザに実行させるのであるから、 プログラムでは次のような順番で作成する必要がある:


def barplot(x) 
   描画方法     棒線グラフ機能の部分
end

def lineplot(x) 
   描画方法     折線グラフ機能の部分
end
               
上部で機能を作っておいて、以下の実行部分で呼び出す

a = gets.chomp!      データを取り込む部分
:

if  ....        棒線グラフが選ばれたら
   barplot(x)   棒線グラフの method を実行
else 
....

操作をまとめる

さきほどの 3 つの関数 f(x) のグラフを書くことを考えよう。

f(x) = 3 * x 
f(x) = 2 * x - 1 
f(x) = - 2 * x + 1 

a = 1 を選んだとき、 それぞれの関数の値はどのようにして得られるだろうか。

f(x) = 3 * a = 3 * 1 = 3
f(x) = 2 * a - 1 = 2 * 1 - 1 = 1
f(x) = - 2 * a + 1 = - 2 * 1 + 1 = -1

f(x) = 3 * x, f(x) = 2 * x - 1, f(x) = - 2 * x + 1f(x) の部分だけ変更するように作る。


def 関数(仮引数)
関数の定義
end

この定義部分、 def -- end を method と呼ぶ。 ちなみに英語では機能も関数も function である。 def_graph.rb


def f(x)
3 * x
end STDERR.print("定義域の範囲はどこまでにしますか (a < x < b)\n") STDERR.print("a = ") a = gets.chomp.to_i STDERR.print("b = ") b = gets.chomp.to_i STDERR.printf("開始座標 (a = %d, f(a) = %d)\n",a,f(a)) STDERR.printf("終了座標 (b = %d, f(b) = %d)\n",b,f(b))

ユーザに入力させたり、結果を知らせたりする必要がある。 それはプログラム実行部分に書く。 def -- end で囲まれる部分が定義である。 あらかじめ定義しておき、 必要に応じて呼び出す。

先ほどの規則から、機能は上に書くので、グラフ機能を付け加えよう。


def barplot(x1,x2)
for i in 0..x1-1
print("|\n")
end print("-" * f(x2) + "- y\n") for j in x1..x2
print("|" + "*" * f(j) + "\n")
end print("|\nx\n")
end : barplot(a,b) 呼出し部分

実行すると

定義域の範囲はどこまでにしますか (a < x < b)
a = 2
b = 8
開始座標 (a = 2, f(a) = 6)
終了座標 (b = 8, f(b) = 24)
------------------------- y
|
|
|
|******
|*********
|************
|***************
|******************
|*********************
|************************
|
x

このグラフは x について傾きが正である場合はグラフの軸が十分な長さで書けるが、 そうでない場合は短くなる。その理由を調べ、改良せよ。

method を使ったプログラムの作り方の特徴

プログラムでのコメントをつけることを通して、 method を使ったプログラムについて考えてみよう。

def -- end の部分にどのような関数の計算であるかを 1 行ずつ説明した。

def -- end には f(x) すなわち 3 * x, 2 * x - 1, - 2 * x + 1 が入る。

def -- end 内では一番最後の行にある変数が f(x) に入る。すなわち y = 3 * x - 2 ならば


def f(x)
 3 * x - 2           x を 3 倍して -2 を加える
end    

でもよいし、


def f(x)
 y = 3 * x           x を 3 倍したものを y とおく
 y - 2               y から -2 する
end                 

でもよいし、


def f(x)
 y = 3 * x           x を 3 倍したものを y とおく
 k = -1              k を -2 と定める
 y += k              y に k を足す
end     

でもよい。最後の行まで計算して y = 3 * x - 2 にさえなっていればよい。

3 * x + 2 のときには、この値をそのまま返すので、 y を定義する必要がない。

他の場合にも実際に作成して同じ結果を得るかどうか調べよ。

手作りルールの最後の値

プログラムでのコメントをつけることを通して、 method を使ったプログラムについて考えてみよう。

def -- end の部分にどのような関数の計算であるかを1 行ずつ説明した。

def -- end には f(x) すなわち 3 * x, 2 * x - 1, - 2 * x + 1, が入る。

def -- end 内では一番最後の行にある変数が f(x) に入る。すなわち y = 3 * x - 2 ならば

def f(x)            #
 3 * x - 2          # x を 3 倍して -2 を加える
end                 #

でもよいし、

def f(x)            #
 y = 3 * x          # x を 3 倍したものを y とおく
 y - 2              # y から -2 する
end                 #

でもよいし、

def f(x)            #
 y = 3 * x          # x を 3 倍したものを y とおく
 k = -2             # k を -2 と定める。
 y += k             # y に k を足す
end                 #

でもよい。最後の行まで計算して y = 3 x - 2 にさえなっていればよい。

3 * x + 2 のときには、この値をそのまま返すので、y を定義する必要がない。

他の場合にも実際に作成して同じ結果を得るかどうか調べよ。

直線グラフ

棒グラフは、x の値を受け取ると、 値域 f(x) までの値を "*" で塗りつぶす。 直線グラフならば、f(x) - 1 までは空白文字 " " で、 f(x) のみ * であればよい。


def lineplot(x1,x2)
  print("-" * f(x2) + "- y\n")
  for i in 0..x1-1
    print("|\n")
  end
  for j in x1..x2 
    print("|" + " " * (f(j) - 1) + "*" + "\n")
  end
  print("|\nx\n")
end

: 

lineplot(a,b)  # 呼出し部分

実行すると

定義域の範囲はどこまでにしますか (a < x < b)
a = 2
b = 6
開始座標 (a = 2, f(a) = 6)
終了座標 (b = 6, f(b) = 18)
線のタイプを選んで下さい
1. 線グラフ表示          2. 棒グラフ表示
2
------------------- y
|
|
|     *
|        *
|           *
|              *
|                 *
|
x

このグラフは x について傾きが正である場合はグラフの軸が十分な長さで書けるが、 そうでない場合は短くなる。その理由を調べ、改良せよ。

ヒストグラム

棒グラフは、x の値を受け取ると、 値域 f(x) までの値を * で塗りつぶすことを、 0 から x まで実行する。 ヒストグラムの場合、Hash を使って表現すればよい。


def histogram(data)
for freq in data
print("*" * freq + "\n")
end
end データを配列に仕込む部分 : histogram(rank)

実行すると 例えば rank = [2,4,6,4,2] が入った状況において、

% ./def_histogram.rb
**
****
******
****
**

配列に仕込む部分にはさらに工夫が必要だが、 具体的に作ってみて、改良部分を考えよ。

帯グラフ

帯グラフの場合、全体を求めておいて、その 100 分率で グラフを配分すれば良い。


def beltgraph(data)
ratio = Array.new 帯グラフの割合をしまう配列 全体の個数を求める tot = 0 data.each{|no|
tot += no
} data.each{|r|
ratio << r.to_f / tot data を 1 つとりだして r にしまい、それを全体で割る。割り算なので少数値変換しておく。
} ratio ratio を返す
end def belt(graph)
col = ["@","#","&","*","&"] パターンは 5 種類にした block = Array.new 何ブロック塗るかをしまう for component in graph
c = component * 75 75 マス分を 100 % に分布させる c = (c + 0.5).to_i マス目の数を四捨五入 block << c
end k = 0 帯の太さ while k < 3
for i in 0 .. block.length - 1 print("#{col[i]}" * block[i]) end print("\n") k +=1
end
end

実行すると 例えば anq = [8, 5, 2] において、

%./def_beltgraph.rb
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#########################&&&&&&&&&&
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#########################&&&&&&&&&&
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#########################&&&&&&&&&&

帯グラフの解説を下側にでもつけるべきだが、各自工夫せよ。

前期の色出力をつけるとさらに見やすくなるので、工夫せよ。

ヒストグラム

棒グラフは、x の値を受け取ると、 値域 f(x) までの値を "*" で塗りつぶすことを、 0 から x まで実行する。 ヒストグラムの場合、Hash を使って表現すればよい。

def histgram(data)
  for freq in data
    print("*" * freq + "\n")
  end
end
  

# データを配列に仕込む部分

  :
histgram(rank)

実行すると 例えば rank = [2,4,6,4,2] が入った状況 において、

% ./def_histgram.rb                     
**
****
******
****
**

配列に仕込む部分にはさらに工夫が必要だが、 具体的に作ってみて、改良部分を考えよ。

Hash の作りかた

Hash.new で、Hash を新たに作る。 3 つの表示方法があった。key は文字列を取ることができる。

Hash 名 = Hash.new(既定値) 新たに Hash を定義するとき
Hash 名["key"] = "value" key に対して value が决まる
Hash 名 = {"key1" => "value1", "key2"=>"value2", ...} 対を並べて書く方法

一方、配列では、 Array.new で作ることができる。第 0 要素から始まる。

配列 = Array.new(全要素数,初期値) 新たに配列を定義するとき
配列[要素n] = "情報n" 情報がある第 n 要素に格納される
配列 = ["情報0","情報1",... , "情報n", ... ] 並べて書く方法

どちらにも初期値を設定することができた。空の要素が存在するときに、 初期値を自動的に入れて返す。5 教科の成績を配列 score にしまうとき、 欠席した場合得点は 0 なので、

score = Array.new(5,0) 配列要素の初期値は 0

と書いた。また、Hashでは、与えられた key に対して value が存在しないときに返す値を設定した。 例えば英和辞書機能 dic という Hashに対し、 登録されていない key が与えられても変換できない。このことを次のように表せる。

dic = Hash.new("不明な単語") # Hash の既定値は "不明な単語"

データの切り取りかた

データを切り取るには正規表現を用いた。データは、 *.dat あるいは *.txt というテキスト形式のデータで、 1 行には特定の情報が順に並べられている。同じ列には同じ属性のものが入る。 名簿などの場合、氏名と 5 教科の成績、などであった。 文字列は \S+, 空白文字列は \s+, 整数値は \d+ で切り取った。


if /(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/ =˜ 変数
   配列や Hash への分解
end

配列では、氏名は name, 成績は japanese, english, ... に入れることにすると


name[0] = $1
japanese[0] = $2
english[0] = $3
:

Hash では、データが


白米      5000
玄米      1000
黒ごま     200

であるとき、

shop[$1] = $2

とプログラムでデータを処理する。 value はそのままでは文字列として処理した。

一つずつ取り出すには / 並べ替えを詳しく行うには

1 つの答えを取り出したり、並べ替えたりすることができた。また、 その表示方法も工夫することができた。

一つずつ取り出すには

each method を使うと、一つずつ取り出して動作を行った。


配列.each{
|配列の要素を代入する内部変数| 内部変数に入った文字列について実行したい動作
}

指定して並べ替えるには

sort method は {} でどこを並べ替えるのか指定することができた。


配列.sort{|変数 A, 変数 B|
変数 A を使って並べ替えに使用する部分を表す <=> 変数 B を使って並べ替えに使用する部分を表す
}
変数 A, B は並べ替え前と後の要素を代入する変数 <=> 並べ替え演算子

中身には何が入っているのか

Method を組み合わせると一度に実行することが可能だが、 p 文などを使用しながら、 何が行われているかを調べつつ組み合わせることが大事である。 小さな method で大きな仕事ができる Ruby 言語の要といえよう。

Value に配列が入る場合の処理

each method を使って、要素を一つ一つ取り出すことができた。 Key を叩いて、value 配列の一つを取り出すときには、 Hash 名[key][要素番号] を指定すればよい。


obj 配列.each{
|反復変数| 反復変数を用いた実行文
}

Value の配列のある要素に対して入れ替えを行うには

each method で key を変数に入れると、value も Hash を使って表せる。 value 配列のある要素に対して sort を行い、変更後の配列を obj 配列とする場合

Hash 名.keys.sort{
|変更前の key 変数,変更後の key 変数|
    Hash 名[変更前の key 変数][要素番号] <=> Hash 名[変更後の key 変数][要素番号]
}

として、key 配列を並べ替えることができる。