解説 9月(1)

今回はプログラミング3の初回です。次のことを行います。

基本知識の確認と復習は、できるところまで行ってください。 しばらく、プログラミングを行っていないので、プログラミングの勘を取り戻 すために行います。必要なら、次回も復習に当てます。

このコースがどんなコースかを理解する

このプログラミング3のコースがどんなコースなのかという概要は、 コースの説明に 書いてあります。そこを見てみましょう。

今日の課題は、久しぶりにプログラミングをする人も少なくないので、プログラミング頭に戻すための復習です。 さっとやってみてください。

基本の確認と復習

まずプログラミングにおける基本的な内容の復習をしておきましょう。 すぐに解決できない場合でも、まず、前に学んだことを想い出しながら、自分の力で解決する努力をしてください。

課題

以下の課題をLinuxにログインして行いなさい。 ファイルの作成・編集や、プログラムの実行など、できる限りLinux上で行うこと。 今後のために、今からLinux環境での作業に慣れておく必要があります。
Linux環境ではファイルがEUC-JPという文字コードで保存されます。 pを使って日本語を表示するときは、ファイルの冒頭に $KCODE = "e" と書いておきましょう。

課題1-1-1

次のように2から256まで数を2倍しながらディスプレイに表示する プログラムを、繰り返し文を使って書きなさい。

2
4
8
16
32
64
128
256
[繰り返し文とは]

同じようなことを「繰り返す」ことは人間には退屈でそのために間違いも しやすいです。それに対して、プログラムでは、繰り返し文が用意され、 間違えずに同じようなことを繰り返し正確に行うことができるようになってい ます。

Rubyでは繰り返し文(イテレータと言います)にはwhileforsteptimesなど、いろいろな種類が用意されています。

ここではwhileforについて復習します。

どちらを使っても構いませんが、できれば、用途によって繰り返し文を使い分 けられるようになると、間違いを起こしにくいプログラムが書けるようになる でしょう。

課題1-1-2

次の配列ary1の要素を逆順に並べた配列を作り表示するプログラムを、繰り返し文を使って書きなさい。

ary1 = ["a", "b", "c", "d", "e", "f", "g"]
ary2 = []

#
#ここを作成する。
#

p ary2
["g", "f", "e", "d", "c", "b", "a"]

[配列に要素を追加するには]

配列の末尾に新たな要素を追加するときは、次のように << を使用すると簡単です。

ary = []

ary << "a"
p ary

ary << "b"
p ary

ary << "c"
p ary
["a"]
["a", "b"]
["a", "b", "c"]

<< を使って要素を追加する場合、新たな要素は必ず配列の一番後ろに追加されます
また、次のように書いても、配列の一番後ろに要素を追加することができます。

ary = []

ary = ary+["a"]
ary = ary+["b"]
ary = ary+["c"]

p ary
["a", "b", "c"]

なぜこうなるか、わかりますか?


また、ここで紹介した以外の他のやり方にはどんなものがあるでしょう?

課題1-2-1

配列には、文字列や数、さらに配列など、さまざまなものを入れることができます。
次の配列aryに何が入っているか、その種類(文字列、数、配列、ハッシュ)と、具体的な値を答えなさい。 0番目の要素は○○で値は××、1番目の要素は○○で値は××というように答えなさい。
また、配列aryの要素はいくつあると考えればよいでしょうか。aryの要素の個数を答えなさい。

ary = [1, [2, [3, 4]], "[5, 6], 7", {"8" => 9}]

課題1-2-2

sizeメソッドやpを使って、課題1-2-1の答えが正しいかどうか検証しなさい。 例えば、配列の一つ目の要素を調べるときは

p ary[0]

とすればよい。間違っていた場合は、どこを間違ったか報告すること。

[sizeメソッドの使い方]

配列に対してsizeメソッドを使用すると、その配列の持つ要素の個数を知ることができます。

ary = ["a", "b", "c"]
p ary.size
3

[printとpの違いは?]

printとpの違いを覚えていますか? 「pを使うと勝手に改行してくれる!」だけではありません。 むしろ大事なのは、値をわかりやすい形で表示してくれるというところです。

printの場合、つぎの3つの値はすべて123と表示されてしまいます。

a = "123"
b = 123
c = [1, 2, 3]

print a, "\n"
print b, "\n"
print c, "\n"
123
123
123

これに対してpは、文字列には " " を、配列には [ ] を付けて表示してくれます

a = "123"
b = 123
c = [1, 2, 3]

p a
p b
p c
"123"
123
[1, 2, 3]

このように、pを使用することで、変数の値や配列の要素が何かを簡単に調べることができます。

[値の種類を確実に見分けるには]

値の種類(クラス名)を調べるときは、classメソッドを使用します。

a = "123"
b = 123
c = [1, 2, 3]
d = {1 => 100, 2 => 200, 3 => 300}

p a.class
p b.class
p c.class
p d.class
String
Fixnum
Array
Hash

文字列はStringクラス、数はFixnumクラス、配列はArrayクラス、ハッシュはHashクラスのオブジェクトです。

これらのクラス名は、エラーメッセージの中でも使われています。

in `+': String can't be coerced into Fixnum (TypeError)
undefined method `to_i' for ["1", "2", "3"]:Array (NoMethodError)

課題1-3-1

次の配列dataには、動物の名前と鳴声の組み合わせが入っています。

data = [["いぬ", "ワン"], ["ねこ", "ニャー"], ["さる", "ウキッ"]]

この配列を使って、キーボードから「いぬ」と入力されたら「ワン!」と表示し、「ねこ」と入力されたら「ニャー!」と表示し、「さる」と入力されたら「ウキッ!」と表示するプログラムを作成しなさい。 繰り返し文とif文を使用して作成すること。

> ruby kadai1-3-1.rb
動物:いぬ
鳴声:ワン!
> ruby kadai1-3-1.rb
動物:ねこ
鳴声:ニャー!
> ruby kadai1-3-1.rb
動物:さる
鳴声:ウキッ!

[キーボードからの入力を受け取るには]

キーボードから文字列の入力を受け取るときはgetsを使います。

animal = gets

p animal

上のプログラムを実行し、キーボードから「いぬ」と入力すると、ディスプレイに次のように表示されます。

"いぬ\n"

キーボードから受け取った文字列には、必ず最後に改行コードが付きます。

[改行コードを取り除くには]

文字列に対してchopメソッドを使うと、最後の1文字を取り除くことができます。
厳密に言うと、最後の1文字ではなく1バイトだけが取り除かれます。 日本語の文字は(Shift-JISの場合)1文字が2バイトですので、半分だけ取り除かれることになります。
なお、最後の文字が改行コードの場合は、"\n" を取り除いてくれます。

p "abc\n".chop
p "あいうえお".chop
"abc"           #"\n" が取り除かれた。
"あいうえ\202"  #"お" が半分だけ取り除かれた。

ちなみに、chopメソッドと似た働きをするchompという便利なメソッドもあります。 chompメソッドは、文字列の最後が改行コードのときだけ取り除くメソッドです。 最後の1バイトが改行コードでないときは何もしません。

p "abc\n".chomp
p "あいうえお".chomp
"abc"           #"\n"が取り除かれた。
"あいうえお"    #改行コードではないので取り除かれなかった。

[二重配列から要素を取り出すには]

次の配列aryの要素は配列です。取り出して確認してみましょう。

ary = [["a", "b"], ["c", "d"], ["e", "f"]]

p ary[0]  #aryの0番目の要素を取り出して表示。
p ary[1]  #aryの1番目の要素を取り出して表示。
p ary[2]  #aryの2番目の要素を取り出して表示。
["a", "b"]
["c", "d"]
["e", "f"]

文字列が2つ入った小さな配列を取り出すことができました。
今度はその小さな配列から、文字列を取り出す方法を考えてみましょう。

ary = [["a", "b"], ["c", "d"], ["e", "f"]]

ary2 = ary[0]  #aryから0番目の要素を取り出して変数に代入。

p ary2         #ary2の値["a", "b"]を表示
p ary2[0]      #ary2の0番目の要素を取り出して表示。
p ary2[1]      #ary2の1番目の要素を取り出して表示。
["a", "b"]
"a"
"b"

このように、小さな配列を一旦変数に代入してしまえば、いつもと同じように要素を取り出すことができます。

慣れてきたら、一旦変数に代入するのではなく、二重配列から直接中の要素を取り出すようにしてみましょう。

ary = [["a", "b"], ["c", "d"], ["e", "f"]]

p ary[0][0]    #["a", "b"]の0番目の要素を取り出して表示。
p ary[0][1]    #["a", "b"]の1番目の要素を取り出して表示。
"a"
"b"

課題1-3-2

上の問題と同じように動作するプログラムを、配列の代わりにハッシュを使用して作成しなさい。 ここでは繰り返し文とif文を使用する必要はありません。

> ruby kadai1-3-2.rb
動物:いぬ
鳴声:ワン!
> ruby kadai1-3-2.rb
動物:ねこ
鳴声:ニャー!
> ruby kadai1-3-2.rb
動物:さる
鳴声:ウキッ!

[ハッシュの使い方]

要素の登録されたハッシュを用意するときは次のように書きます。 ここで、=>の左をキー、右を値といいます。 たとえば、"JPN"はキーで、その値が"日本"となっています。 ここではキーと値の組み合わせが3組書かれています。

data = {"JPN" => "日本", "USA" => "アメリカ", "CHN" => "中国"}
p data
この結果、次のように表示されるでしょう。ここで、登録した順番とは 必ずしも表示は一致しません。これは、ハッシュと配列との大きな違いです。 配列は登録した要素の順番がそのままインデックス(キー)となり、 それを手掛かりとして要素(値)を取り出します。しかし、ハッシュでは、 順番ではなくキー(この例では"JPN"や"USA"など)が、値を取り出す手掛かりとなっています。
{"USA"=>"アメリカ", "JPN"=>"日本", "CHN"=>"中国"}

次のように、要素を一つずつ登録していくこともできます。

data = {}
data["JPN"] = "日本"
data["USA"] = "アメリカ"
data["CHN"] = "中国"

p data
{"USA"=>"アメリカ", "JPN"=>"日本", "CHN"=>"中国"}

ハッシュから値を取り出すときは次のようにキーを指定します。

p data["JPN"]
"日本"

指定されたキーが登録されていない場合はnilが返されます。

p data["ABC"]
nil

課題1-3-3

次のような、「いぬ」や「ねこ」などの動物の名前が一行に一つずつ書かれたファイルを読み込み、どの動物が何回書かれているかカウントするプログラムを作成しなさい。 ファイルに書かれている動物の種類が増えても対応できるように作成すること。

animals.txt

上のファイルの場合、次のような結果になります。

いぬは全部で6匹います。
ねこは全部で10匹います。
さるは全部で3匹います。
...(以下省略)...

動物を表示する順序は同じでなくてもかまいません。正しくカウントできていればよい。

[ファイルの読み込み]

ファイルの内容を読み込むときは次のようなプログラムを書きます。

$KCODE = "e"    #pを使って全角文字を表示するときに必要。

fo = open("test.txt", "r")  #読み込みモードでファイルを開く。
lines = fo.readlines        #開いたファイルからすべての行を読み込む。
fo.close                    #開いたファイルを閉じる。

p lines         #表示して確認してみましょう。

[ヒント:動物ごとの数を数えるには]

どの動物が何回出てきたか記録しておく必要がありますが、このような場合、ハッシュを上手に使うことで比較的容易に作ることができます。 ハッシュのキーと値に何を当てはめればよいか考えてみましょう。

{キー1 => 値1, キー2 => 値2, キー3 => 値3, ...}

配列を使って作成することもできますが、ハッシュを使った場合と比べてプログラムが複雑になります。 それでも配列を使って作成したい人は、次のように二重配列を使い、動物の名前とカウントした数をセットにして記録しておくとよいでしょう。

[["いぬ", 6], ["ねこ", 10], ["さる", 3], ...]

[ヒント:ハッシュを使って動物ごとの数を数えるには]

最初に空のハッシュを用意しておきます。

h = {}

このハッシュに、動物の名前をキーとして、ファイルに書かれている回数を記録することにします。 例えば "いぬ" と "ねこ" が1回ずつ出てきた場合は、ハッシュは次のような状態になります。

{"いぬ" => 1, "ねこ" => 1}

ここでさらに "ねこ" が出てきたときの処理を考えてみましょう。 ハッシュには既に "ねこ" が登録されていますので、これまでの登場回数に1を足した数を再登録します。

h["ねこ"] = h["ねこ"] + 1

この時点でハッシュは次のような状態になります。

{"いぬ" => 1, "ねこ" => 2}

今度は "さる" が出てきたときの処理を考えてみましょう。 "さる" はまだハッシュに登録されていませんので、新たに "さる" の登場回数をハッシュに登録します。 初登場ですので登場回数は1です。

h["さる"] = 1

ハッシュは次のような状態になります。

{"いぬ" => 1, "ねこ" => 2, "さる" => 1}

このように、登場回数が既にハッシュに登録されている場合は1を足し、登録されていない場合は登場回数を1として新たにハッシュに登録します。

ハッシュはキーが登録されていない場合にnilを返しますので、その性質を利用して、どちらの処理を行うか判別するとよいでしょう。

h = {"いぬ" => 1, "ねこ" => 2, "さる" => 1}

p h["ねこ"]
p h["わに"]
2    #"ねこ"の登場回数は2回。
nil  #"わに"はまだ登場していない。

ハッシュを使ってプログラムを作成する人は次の解説を参照してください。

[ハッシュからすべてのキーや値を取り出すには]

カウントした動物の数を表示するときには、登録されているすべてのキーや値の情報を、ハッシュから取り出す必要があります。

いぬは全部で6匹います。
ねこは全部で10匹います。
さるは全部で3匹います。
...(以下省略)...

そのようなときは、Hashクラスのkeysメソッド、valuesメソッドを使用します。 これらのメソッドをハッシュに対して使用すると、登録されているすべてのキー(または値)を集めた配列を取得することができます。

data = {"JPN" => "日本", "USA" => "アメリカ", "CHN" => "中国"}

p data.keys
p data.values
["USA", "JPN", "CHN"]
["アメリカ", "日本", "中国"]

また、次のように、for文を使ってキーと値を一つずつ取り出すこともできます。

data = {"JPN" => "日本", "USA" => "アメリカ", "CHN" => "中国"}

for k, v in data
  p k     #キー
  p v     #値
end

for pair in data
  p pair  #キーと値の配列
end
"USA"                 #キー
"アメリカ"            #キー
"JPN"                 #キー
"日本"                #値
"CHN"                 #値
"中国"                #値
["USA", "アメリカ"]   #キーと値の配列
["JPN", "日本"]       #キーと値の配列
["CHN", "中国"]       #キーと値の配列

課題1-4-1

次のプログラムの関数searchは、アドレス帳ファイル(address_list.txt)を検索するための関数です。
下のアドレス帳ファイルを自分のフォルダに保存し、プログラムを実行したときにディスプレイに何が表示されるか予測して報告しなさい。 その後、実際に実行して、予測と合ったかどうか報告しなさい。 予測と合わなかったら、なぜそのように表示されるのか考え、説明しなさい。

アドレス帳ファイル
def search(name)
  fo = open("address_list.txt", "r")    #読み込みモードでファイルを開く。
  lines = fo.readlines
  fo.close
  i = 0
  while(i < lines.size)
    line = lines[i]
    data = line.split
    if(data[0] == name)
      return(data[1])
    end
    i = i + 1
  end
  return(nil)
end

p search("中京太郎")
p search("中京次郎")
p search("中京花子")

[文字列の分割(split)]

文字列に対してsplitメソッドを使うと、半角スペースの位置で文字列を分割することができます。

p "aaa bbb ccc".split
p "あい うえ お".split
["aaa", "bbb", "ccc"]
["あい", "うえ", "お"]

実は半角スペースだけでなく、改行コードの位置でも分割されます。

p "あい うえ お\nかき くけ こ\n".split
["あい", "うえ", "お", "かき", "くけ", "こ"]

引数を指定すると、任意の文字の位置で分割できるようになります。

p "たもじたれつたをたぶんたたかつたしますた".split("た")
["", "もじ", "れつ", "を", "ぶん", "", "かつ", "します"]

課題1-4-2

次のプログラムの関数addは、アドレス帳ファイル(address_list.txt)に名前とメールアドレスを登録するための関数です。
プログラムを実行した後にアドレス帳ファイルに何が書かれているか予測して報告しなさい。 その後、実際に実行して、予測と合ったかどうか報告しなさい。 予測と合わなかったら、なぜそのような結果になるのか考え、説明しなさい。

def add(name, address)
  fo = open("address_list.txt", "a")    #追加書き込みモードでファイルを開く。
  fo.print name, " ", address, "\n"
  fo.close
end

add("中京花子", "hanako@chukyo-u.ac.jp")

[追加書き込みモード]

「書き込みモード」("w")でファイルを開くと、元のファイルの内容はすべて消えてしまいます。 元のファイルの内容を残し、最後に何行か書き足したいときは、「追加書き込みモード」("a")でファイルを開くとよいでしょう。 追加書き込みモードでは、それまでのファイルの内容をそのまま残し、一番最後の位置から書き込みを開始することができます。

fo = open("test.txt", "a")   #追加書き込みモードでファイルを開く。
fo.print "書き込みのテスト"  #開いたファイルの末尾に書き込む。
fo.close                     #開いたファイルを閉じる。

課題1-4-3

関数searchとaddを使用して、次のようなアドレス帳プログラムを作成しなさい。

キーボードから入力した名前でアドレス帳ファイルを検索する。
該当者がいる場合、その人のメールアドレスが表示される。
該当者がいない場合、新たにメールアドレスを登録することができる。

実行結果は次のようになります。

#入力された名前が既に登録されている場合

名前を入力してください。
名前:中京太郎
中京太郎さんのメールアドレスは taro@chukyo-u.ac.jp です。
#入力された名前が登録されていない場合

名前を入力してください。
名前:中京花子
中京花子さんはまだ登録されていません。
アドレス帳に登録しますか(yes/no)? yes
中京花子さんのメールアドレスを入力してください。
メールアドレス:hanako@chukyo-u.ac.jp
中京花子さんのメールアドレスを登録しました。

課題1-4-4

アドレス帳ファイル内の、指定された人物のメールアドレスを書き換える関数updateを定義し、その関数を使用して、アドレス帳プログラムに次のような機能を追加しなさい。

キーボードから入力した名前がアドレス帳ファイルに登録されている場合、
その人のメールアドレスを表示して、
さらに別のメールアドレスに変更することもできる。

実行結果は次のようになります。

名前を入力してください。
名前:中京次郎
中京次郎さんのメールアドレスは jiro@chukyo-u.ac.jp です。
メールアドレスを変更しますか(yes/no)? yes                #
中京次郎さんの新しいメールアドレスを入力してください。    #この部分の機能を
メールアドレス:ziro@chukyo-u.ac.jp                       #追加する。
中京次郎さんのメールアドレスを変更しました。              #

[ヒント]

メールアドレスを変更する場合、アドレス帳ファイルに記録されているメールアドレスを書き換えなければいけません。

中京太郎 taro@chukyo-u.ac.jp
中京次郎 jiro@chukyo-u.ac.jp  #このメールアドレスを書き換えたい。
中京花子 hanako@chukyo-u.ac.jp

ファイルの内容を一部分だけ変更するにはどうすればよいでしょう。一緒に考えてみましょう。

書き込みモード("w")でファイルを開くと、そのファイルの中身は空になってしまいます。 そこでまず、ファイルに書かれている情報をすべてプログラムに読み込んでおいて、その後、書き込みモードでファイルを開き直すことにします。 この時点でファイルの中身は空になってしまいますが、アドレス帳の情報は事前にプログラムに読み込んでありますので、あとは変更後のアドレス帳の情報をファイルに書き戻せばよいでしょう。

ファイルに情報を書き戻すときには、指定された人物のメールアドレスの部分だけ、新しいメールアドレスに置き換えるようにします。

中京太郎 taro@chukyo-u.ac.jp     #ファイルから読み込んだ文字列
中京次郎 ziro@chukyo-u.ac.jp  #キーボードから入力された文字列
中京花子 hanako@chukyo-u.ac.jp   #ファイルから読み込んだ文字列