解説 4月12日B

学習項目です。


復習

[プログラムが止まらなくなったら?]

注意:whileを使って間違ったプログラムを作ってしまうとプログラムが
   止まらなくなってしまうことがあります。
  そのような時、Ctrlキーを押しながら C のキーを押すと、
 プログラムは途中で強制的に終了します。

課題0-1

次の配列の中から、偶数をとり出し、表示するプログラムを作成しなさい。

numbers = [4,12,45,21,18,7,20,5,51,16,10]
#この後の部分を作る

出力は以下のようになる。

4
12
18
20
16
10
[偶数の判定]

ある数が偶数かどうかは、2で割って余りが0となるかどうかで決定される。 ちなみに余りが1となるのは奇数という。

課題0-2

次の配列の中から、3の剰余が奇数となる数をとり出し、表示するプログラムを作成しなさい。

numbers = [4,12,45,21,18,7,20,5,51,16,10]
#この後の部分を作る

出力は以下のようになる。

4
7
16
10
[3の剰余が奇数]

「剰余」とは「割った余り」のこと。3の剰余は、必ず0、1,2のいずれか。 だから、3の剰余が奇数ということは、3で割って余りが1となることと同じ。

課題0-3

次の配列の中から、5の剰余が1または2となる偶数を取り出し、表示するプログラムを作成しなさい。

numbers = [4,12,45,21,18,7,20,5,51,16,10]
#この後の部分を作る

出力は以下のようになる。

12
16
[5の剰余が1または2となる偶数]

「5の剰余が1または2の偶数」というのは、「5の剰余が1または2」かつ 「偶数」という2つの条件を満たす数のこと。

この2つの条件を「かつ」(and)で結んだ条件を書けば良いのです。 ここで「かつ」は && で表現されます。

なお、「5の剰余が1または2」という条件は「5の剰余が1」と「5の剰余が2」 という2つの条件を「または」(or)で結んだ条件です。「または」は || で表されます。

配列から配列を作る

プログラムの中で、新しい内容の配列を作ることが必要になることがあります。 配列に新しい要素を加える方法のひとつに、次のように << を使う方法があります。
data1 = ["日曜","月曜","火曜","水曜","木曜","金曜","土曜"]
data2 = []

data2 << data1[3]
data2 << data1[0]

p data2
このプログラムの実行結果は、次のようになります。
["水曜","日曜"]
つまり「配列 << 要素」とは、配列に要素を付け加える (配列の長さがひとつ長くなり、付け加えられた要素はその配列の最後の要素として加わる) という命令です。

課題1-1

次のプログラムを実行したとき、ディスプレイに何が表示されるのか、実際に実行せずに 一歩一歩プログラムを追いかけることで予測し報告しなさい。 次に、実際に実行してみて、予測と合致したかどうか確かめ、その結果も報告しなさい。 予測と会わなかった時は、その理由も考察して報告すること。
$KCODE = "s"
data = ["日曜","月曜","火曜","水曜","木曜","金曜","土曜"]

data2 = []
i = 0
while(i < data.size)
  data2 << data[i]
  print "no=", i, " "
  p data2
  sleep(1)
  i = i + 1
end

課題1-2

変数dataの値の配列に対して、その要素を逆順にならべた配列を作り、 その配列をp で出力するプログラムを作成しなさい。

作った逆順の配列をpで出力すること。

$KCODE = "s"
data = ["日曜","月曜","火曜","水曜","木曜","金曜","土曜"]
data2 = []
#...
#ここを作ること
#...
p data2
このプログラムの実行結果は、次のようになります。
["土曜", "金曜", "木曜", "水曜", "火曜", "月曜", "日曜"]

課題1-3

変数numbersの値の配列の中から、10以上、20以下の数をとり出し、 それを集めた配列を作(り変数newaryの値となるようにす)るプログラムを完成させなさい。
newary = []
numbers = [4,12,45,21,18,7,20,5,51,16,10]
#...
#ここを作ること
#...
p newary

出力は以下のようになる。
[12,18,20,16,10]

課題1-4

次の配列 numbers の値の配列の要素で、変数axisの値よりも小さい数は変数lowerの値の配列の要素となるように、 また大きい数は変数higherの値の配列の要素となるようにするプログラムを完成させなさい。
numbers = [4,12,45,21,18,7,20,5,51,16,10]
axis = 15
lower = []
higher = []
#...
#ここを作ること
#...
print "higher = "
p higher
print "lower = "
p lower

出力は以下のようになる。
higher = [45, 21, 18, 20, 51, 16]
lower = [4, 12, 7, 5, 10]

複雑な構造を持つ配列

Ruby言語の配列には、文字列オブジェクトや数のオブジェクトを入れることができました。 実は、配列オブジェクトには、変数と同様に、どんなオブジェクトでも格納することができます。 配列もまたオブジェクトですから、配列の中に別の配列を入れることもできます。 次の例を見てください。
ary = [15,32,[100,320,170],403,["こねこ",32]]

課題1-5

次のプログラムを実行すると、どのような出力がでてくるのか実行せずに予測し報告しなさい。 次に、実際に実行して、自分の予測と合致したかどうか報告しなさい。 予測と違った場合はなぜ違ったのかを検討し、違った理由も報告しなさい。
ary = [15,32,[100,320,170],403,["こねこ",32]]

#no1
p ary.size

#no2
p ary[2]
p ary[2].size
配列の要素に配列が入っているような複雑な配列でも、その扱いはこれまでと同じです。 これまでの規則を順番に一歩一歩適用していきます。例えば、次のような形で、配列の中にある配列の要素を取り出すことができます。
ary = [15,32,[100,320,170],403,["こねこ",32]]

p ary[4][0]
結果はもちろん、次のようになります。
"こねこ"

課題1-6

次の配列は曜日計算をするように、曜日と数を結び付けている。この配列の内容を以下のように書き出すプログラムを作成しなさい。
data = [["日曜",0],["月曜",1],["火曜",2],["水曜",2],["木曜",4],["金曜",5],["土曜",6]]
#以下の部分を作る
日曜=>0
月曜=>1
火曜=>2
水曜=>3
木曜=>4
金曜=>5
土曜=>6

負の指標(インデックス)

今までは、配列の指標は0以上の整数だと書いてきましたが、 実際にはRubyでは負の指標も許しています。 次のプログラムを実行してみてください。
data = ["a","b","c"]
for i in 1..3
  print "data[",-i,"]=",data[-i],"\n"
end
この結果は次のようになります。
data[-1]=c
data[-2]=b
data[-3]=a

課題1-7

上の例から、配列の指標が負の場合、配列のどの要素が取り出されるでしょうか。 それに対する考えを述べ、別な配列を用いてそのその考えの正しさを確かめなさい。

課題1-8: 配列の大きさよりも大きな指標(インデックス)が使われた場合

指標として、配列の大きさよりも大きな指標が使われた場合でも、 Rubyはエラーではなく、特定の値を返します。 次のプログラムを実行し、その結果を答えなさい。
data = ["a","b","c"]
for i in 0..5
  print "data[",i,"]=",data[i],"\n"
end
[nilについて]

nil は「何もない状態」を示すのに用いられるものです。この課題の場合、 指標で指定された配列の要素には「何もない」ので、意味がぴったりあっていますね。
なお、nil は変数のように見えますが、これは特殊な存在のオブジェクトです。 あとで学ぶクラスとインスタンス、という用語を使うと、NilClassのインスタンスなのです。 そして nil = 1 のようにnilに代入しようとするとエラーが起こります。

オブジェクト

これまでに学んできたプログラミングでは、その対象として"今日は"のような文字列や、 1234のようなを主に扱ってきました。 さらに、文字列や数を格納できる配列が新たに登場しました。 このように、変数の中に格納することができ、命令、操作の対象となるものを一般に オブジェクトと呼びます。 これから、もっといろいろな種類のオブジェクトを扱っていくことで、 より実際的で効果的なプログラミングを可能にしていきます。 今後の学習を進めていく上で、オブジェクトという言葉を使えるようになることが必要です。

オブジェクトの種類

これまで扱ってきた文字列オブジェクトには String という正式名称があります。 英語で「文字列」の意味です。数の場合には、整数と小数点のついた数(浮動小数点数、といいます)が区別されていますが、それぞれFixnumとFloatというのが正式名称です。 ただ整数の中でも、非常に大きな数はBignumという呼び名がつけられています。 なお、配列の正式名称はArrayです。
場合によって、このような名前を取り出せると便利なことがあります。 次のような方法でオブジェクトからそのオブジェクトの名前を取り出すことができます。 それをプログラムの中で使う練習を後でやります。
このような、ドットつまり、"." を使って、オブジェクトを対象にして、 いくつか命令をつなぎ合わて作る命令の表現を、ドットによる命令表現という意味で、 ドットノーテーションと呼ぶことがあります。
obj = "みけねこ"
p obj.class.name
"String"

課題2-1

次のそれぞれのオブジェクトの正式名称(クラス名)を上の方法を使って調べて報告しなさい。
"みけねこ"
200
31.5
500000000000000000
[23,15,8]
"みけねこ"           => String
200                  => #ここを報告
31.5                 => #ここを報告
500000000000000000   => #ここを報告
[23,15,8]            => #ここを報告

課題2-2

オブジェクト.class.nameというドットノーテーションで結ばれた命令を使って、オブジェクトの種類を文字列として取り出すことができましたが、これを利用して次の問題を解決しなさい。
以下の配列には、文字列と数が混在している。配列の中から文字列だけを取り出し、出力するプログラムを作成しなさい。
aray = [1200, 23, "ばけねこ","こねこ","こねこ",11,10,"みけねこ",25,"うみねこ","こねこ"]
#この後の部分を作る
ここで、出力の形式は以下のようなものとします。
ばけねこ こねこ こねこ みけねこ うみねこ こねこ

ファイル

ここではファイルについて学びます。 ファイルは、現代のコンピュータシステムの基本要素の一つです。 ワードプロセッサーや表計算ソフトのように、どんなアプリケーションプログラムでもファイルを使います。

実際、実用的なプログラムを作る上で、ファイルをプログラムの中から扱かえるようにすることは不可欠です。 ファイルから情報を読み込んで処理したり、その結果をファイルの中に書き込んだりすることが必要になります。

実は、ファイルをプログラムで扱うこと自体はそれほど難しいことではありません。 以下で、プログラムの中からファイルを扱う方法を学びましょう。

ファイルへの書き出し

次のプログラムを実行すると、コマンドプロンプトのウインドウに名前を出力します。
ary = ["中京太郎", "豊田次郎", "八事花子"]

for name in ary
  print "名前:",name , "\n"
end
名前:中京太郎
名前:豊田次郎
名前:八事花子
この結果を、ファイルにとっておくためには、少し準備が必要です。 結果をとっておくファイル名を"names.txt"にすることにします。
まず、次の命令を実行して、ファイルを扱えるようにしなければなりません。
fo = open("names.txt", "w")

特定の名前のファイルを開いて(openして)、そのファイルを扱うための仕掛け(ファイルオブジェクトと呼びます)を、 適当な変数(上の場合はfo)に代入しておきます。 プログラムの中では、そのファイルオブジェクトに対して、書いたり読んだりする操作をしていきます。

openという命令の、ファイル名の次にある"w"は、ファイルを書き出し(Write)の形にするためのものです。(ここでその名前のファイルがある場合には、 その中身が消去されることに注意してください。 なお、中身を消去せずに「追加書き」するものや、 ファイルから読み込みするものは後で述べます。) ファイル名も書き出しの"w"も、どちらも文字列で表します。 二重引用符(ダブルクォート)を忘れないように気を付けましょう。

fo = open("ファイル名", "w")
これだけ準備しておけば、後は簡単です。
ary = ["中京太郎", "豊田次郎", "八事花子"]

fo = open("names.txt", "w")

for name in ary
  fo.print "名前:",name, "\n"
end

fo.close
このプログラムを実行すると、names.txtというファイルが作られ、print文の出力が書き込まれます (既にファイルがある場合は上書きされます)。 print文の前に fo. と付け加えられていることに注意してください。 これまでのprint文と違い、この場合は、 変数foの中のファイルオブジェクトに対して書き出すという操作を行います。
プログラムの最後に付いているfo.closeという命令は、変数foの中のファイルオブジェクトに対し、ファイルへの書き込みが終わったので、 後始末の処理(ファイルの保存など)をするためものです。 この命令を書き忘れても当面は何も困ったことは起こりませんが(プログラム終了時に自動的にcloseされます)、習慣として書くようにしましょう。 言い換えれば、ファイルは一度開けたら(openしたら)、閉めて(closeして)おいたほうがよいのです。

課題3-1

上のファイルの書き出しのプログラムを実行して、実際にnames.txtというファイルが作成されて、中身がprint文の出力になっているかどうか、Terapadなどを使って確かめて報告しなさい。

課題3-2

上で、実行したファイルへの書き出しプログラムと、その前に例として出ているコマンドプロンプトwindowへ書き出すプログラムを比較し、どこが違うのか、違う点を箇条書きにして報告しなさい。

ファイルへの追加書き

先の課題で、names.txt があるとします。ここで、 ファイルを開く(openする) ときにopen命令で"w"の代わりに、"a"としてみましょう。
ary = ["名古屋政美", "愛知京子", "中部喜朗"]

fo = open("names.txt", "a")
for name in ary
  fo.print "名前:",name, "\n"
end

fo.close
このプログラムを実行すると、すでにあるnames.txtというファイルの中身は 消されずに、その後ろに、print文の出力が書き込まれます。 これを「追加書き」(Append)といいます。

課題3-3

上のプログラムを実行し、names.txt ファイルの中身を確かめよ。

ファイルからの読み込み

今度は、プログラムでファイルの中身を読み込む方法を学びます。
ファイルを読み込むためには、読み込む対象となるファイルが存在している必要があります。 上で作ったnames.txtの中身を読み込むことにしましょう。 names.txtという名前のファイルが存在し、その中身が次のようなものであることをTerapadなどで確認しておいてください。

名前:中京太郎
名前:豊田次郎
名前:八事花子

まず、ファイルを扱えるようにしなければならないところは、ファイルの書き出しとほとんど同じです。 書き出しと同様、特定の名前のファイルを開いて(open)して、それを扱うための仕掛け(ファイルオブジェクト)を、 適当な変数(以下の場合はfo)に入れておき、それに対して、読む操作をしていきます。

fo = open("names.txt", "r")

ちなみに、今回はファイルから文字列の「読み込み」を行うので、 右辺の括弧の中の二つ目の文字列が読み込み(Read)を指定する"r"になっています。 (書き出しの場合は"w"でした。)

この準備をしておき、getsを使うと、ファイルの内容を1行だけ読み込みます。 これまでの、キイボードから文字列を入力する(読み込む)ときに使ったgetsと同じです。

$KCODE = "s"  #pを使って日本語を表示するときに必要です。

fo = open("names.txt", "r")

line1 = fo.gets  #1行目の読み込み。
line2 = fo.gets  #2行目の読み込み。
line3 = fo.gets  #3行目の読み込み。
line4 = fo.gets  #4行目の読み込み。

fo.close

p line1
p line2
p line3
p line4
"名前:中京太郎\n"
"名前:豊田次郎\n"
"名前:八事花子\n"
nil

getsで読み込まれた1行分の文字列には、改行文字("\n")もついていることに注意してください。
最後のnilというのは、「無」を表す値です。 names.txtには、4行目以降何も書かれていませんので、文字列ではなく、nilが表示れます。

次のプログラムはnames.txtのファイルの内容を、getsによって読み込んでは、すぐに出力することを繰り返し、ファイルの内容をすべて表示するプログラムです。

fo = open("names.txt", "r")

while(line = fo.gets)  #nilを読み込むまで繰り返す。
  print line
end

fo.close

ファイルからの読み込みは、whileの条件式のところで行っています。 実はwhileは、条件式の値がfalseかnilになるまで繰り返しを続けます。 この場合は、ファイルからの読み込みの値がnilになるまで、読み込みと出力を繰り返します。

課題3-4

getsなどを使って、課題3-1で作成した names.txtというファイルの内容を一行づつ読み込み、コマンドプロンプトのウインドウにそのまま出力するプログラムを作成しなさい。

getsではなくreadlinesを使ったファイルからの読み込み

ファイルは便利なものですから、それを扱うための命令もたくさんあります。 ファイルからの読み込みも種種ありますが、ここでは、readlinesという、ファイル全体を一度に読み込む命令を学んでおきましょう。
まず、次の命令を実行して、ファイルを扱えるようにしなければならないところは、 これまでの、ファイルの書き出し、読み込みと同じです。 特定の名前のファイルを開いて(open)して、それを扱うための仕掛け(ファイルオブジェクト)を 適当な変数、(以下の場合はfo)に入れておき、それに対して、読む操作をしていきます。 ちなみに、今回はファイルからの文字列の「読み込み」を行うので、右辺の 括弧の中の二つ目の文字列が読み込みを指定する"r"になっています。 (書き出しできるようにする場合は"w"でした。)

この準備をしておき、readlinesを使うと、ファイルの内容を全体として 配列の中に読み込めます。 内容は配列の中に、一行が一つの配列の要素として格納されます。 次のプログラムはnames.txtのファイルの内容をreadlinesを使って aryという配列の中に読み込み、その配列の内容をpによって、 見えるようにしたものです。

$KCODE = "s"
ary = []

fo = open("names.txt", "r")

ary = fo.readlines
p ary

fo.close

課題3-5

fo.readlinesの結果が返すものを詳しく検討して、getsが返すものと何が違うのか述べなさい。
文字列か配列かなど。もし配列であるならば、配列の要素は何で、要素の数は何に対応するのかなどもできるだけ詳しく述べること。

文字列の分割

ファイルの内容を読み込んだ場合などもそうですが、文字列を分割する必要がでてくることが少なくありません。
文字列を分割することで、その内容を処理することができるからです。
次のsplit文字列オブジェクトに対して実行すると、スペースのところで分割され、分割されたものを要素とする配列ができあがります。
str = "こねこ おやねこ こねこ こねこ うみねこ こねこ のらねこ みけねこ こねこ"
ary = str.split
p ary
実行結果は次のようになります。
["こねこ", "おやねこ", "こねこ", "こねこ", "うみねこ", "こねこ", "のらねこ", "みけねこ", "こねこ"]

課題3-6

上の結果を利用して、次のスペースで区切られた文字列の中に「こねこ」が何匹いるのか数えて出力するプログラムを作成しなさい。 表示の形式は指定された形にすること。
str = "こねこ おやねこ こねこ こねこ うみねこ こねこ のらねこ みけねこ こねこ"
#この後の部分を作る

出力の形は以下のようにすること。
「こねこ」の数は全部で5匹です。

課題3-7

nekos.txtというファイルの中には、いろいろな「ねこ」が文字列として入っています。このファイルの内容を読み込み、そのまま書き出すプログラムをgetsを使って作成してください。
出力は次のようになる。
しろねこ みけねこ みけねこ しろねこ しろねこ みけねこ しろねこ しろねこ みけねこ
みけねこ しろねこ みけねこ しろねこ みけねこ みけねこ しろねこ
くろねこ くろねこ しろねこ
みけねこ みけねこ しろねこ くろねこ くろねこ しろねこ みけねこ
みけねこ しろねこ みけねこ くろねこ しろねこ みけねこ
しろねこ みけねこ
しろねこ ばけねこ
...
...省略
...
しろねこ しろねこ くろねこ しろねこ
くろねこ しろねこ
くろねこ しろねこ しろねこ
しろねこ しろねこ しろねこ みけねこ みけねこ しろねこ くろねこ
しろねこ
くろねこ みけねこ みけねこ
しろねこ みけねこ みけねこ しろねこ くろねこ しろねこ くろねこ くろねこ
くろねこ みけねこ しろねこ みけねこ みけねこ みけねこ

課題3-8

課題3-6と課題3-7で作ったプログラムを元にして、「とらねこ」が何匹いるのかを数えるプログラムを作成しなさい。

[ヒント1]

課題3-7では、ファイルの内容を1行ずつ読み込むプログラムを作成しました。 読み込んだ行は、次のような文字列として扱うことができます。

#1行目
"しろねこ みけねこ みけねこ しろねこ しろねこ みけねこ しろねこ しろねこ みけねこ"

#2行目
"みけねこ しろねこ みけねこ しろねこ みけねこ みけねこ しろねこ"

#3行目
...

課題3-7では読み込んだ文字列をそのまま出力していましたが、今度は「とらねこ」の数を数えなければいけません。 ねこの数を数えるのは、課題3-6のプログラムが参考になりそうですね。

[ヒント2]

ファイルから1行読み込むたびに、「とらねこ」の数を数え、合計に加算していく必要があります。

合計は最初は0匹。
while...  #ファイルから1行ずつ読み込む。

  「とらねこ」の数を数える。

  合計に「とらねこ」の数を加算する。
end

「とらねこ」の数を数える所は、課題3-4で作成した通りですので、おそらく次のような形になるでしょう。

  str = 1行分の文字列
  ねこの文字列を分割する。
  「とらねこ」は最初0匹。
  while...  #ねこを1匹ずつチェックする。(for文を使ってもよい。)
    「とらねこ」なら1匹加算する。
  end

このプログラムを、上の合計を数えるプログラムに組み込む必要がありますので、完成すると次のような形になるはずです。

合計は最初は0匹。
while...  #ファイルから1行ずつ読み込む。

  str = 1行分の文字列
  ねこの文字列を分割する。
  「とらねこ」は最初0匹。
  while...  #ねこを1匹ずつチェックする。(for文を使ってもよい。)
    「とらねこ」なら1加算する。
  end

  合計に「とらねこ」の数を加算する。
end

ファイルから1行ずつ読み込む繰り返しの中で、ねこを1匹ずつチェックする繰り返しを行います。 (while文の中にwhile文が入ります。)


「アルゴリズムとデータ構造」のホームに戻る