解説 4月19日A

今回の学習項目です。


復習

課題1-1

次の配列の中の要素の和を表示するプログラムを作成しなさい。

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

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

209

課題1-2

次の配列は要素として配列をもっている。 要素となっている配列それぞれに対し、要素の和を表示するプログラムを作成しなさい。

target = [ [8, 16, 11], [3, 6, 4], [9, 17, 18, 16] ]
#この後の部分を作る

出力例は次のとおり:

35
13
60
[配列の要素がまた配列...]

この問題では変数targetの値が配列でした。その要素を取り出すには、 forを使うと次のように書けます。

  for numbers in target
    #繰り返しのたびに、numbersにはtargetの要素である配列が入る
  end
例えば最初の繰り返しでは、変数numbersの値は [8, 16, 11] という配列になります。 次の繰り返しでは[3, 6, 4]が、最後の繰り返しでは [9, 17, 18, 16] が入ります。
ですからこの問題を解くには、繰り返しのたびに、numbersの配列の要素の和を求め、 それを表示すれば良いことになります。

課題1-3

次の配列は要素として配列をもっている。 要素となっている配列それぞれに対し、偶数の要素の和を表示するプログラムを作成しなさい。

target = [ [8, 16, 11], [3, 6, 4], [9, 17, 18, 16] ]
#この後の部分を作る

出力例は次のとおり:

24
10
34
[偶数の判定]

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

関数

関数とは何か 1

関数によって、役に立つプログラムに名前をつけ、 その名前を使によりプログラムを呼び出して利用することができるようになります。 これがプログラミングの中での関数働きの基本です。

次のプログラムは中京大学の住所を表示するものです。

print "〒470-0393  豊田市貝津町床立101\n"
print "tel:0565-45-0971\n"

さて、中京大学の豊田キャンパスには複数の学部がありますが、それぞれの学部とその住所を 次々に表示したい場合には、この部分を繰りかえし使えばできます。

print "中京大学情報理工学部"
print "\n"
print "〒470-0393  豊田市貝津町床立101\n"
print "tel:0565-45-0971\n"
print "\n"

print "中京大学現代社会学部"
print "\n"
print "〒470-0393  豊田市貝津町床立101\n"
print "tel:0565-45-0971\n"
print "\n"

print "中京大学体育学部"
print "\n"
print "〒470-0393  豊田市貝津町床立101\n"
print "tel:0565-45-0971\n"
print "\n"

上のプログラムでは大学の住所をプリントするのに全く同じことを 繰りかえし3回書いていますが、繰り返すところがちょっと無駄な感じがします。 プログラムをコピーして張り付ける手間も面倒です。 もっとよいやり方が実はあります。この部分について名前をつけて、その名前を使って、プログラムを呼び出す仕組みを使うこ とでができます。 そうすることで、プログラムを書く手間も減るだけでなく、 プログラムの見通しもよくなります。

具体的には、次のようにします。大学の住所を書く部分を「関数」として取り出し、適当な名前をつけます。 関数定義の形式であるdef...endを使って、名前と実体を登録する部分を作っておき、それを プログラムの一番先頭においておきます。
次に、その名前を必要なところで、繰り返し使ってプログラムを書きます。
次のようになります。

def daigaku_Jusho
    print "〒470-0393  豊田市貝津町床立101\n"
    print "tel:0565-45-0971\n"
end

print "中京大学情報理工学部"
print "\n"
daigaku_Jusho
print "\n"

print "中京大学現代社会学部"
print "\n"
daigaku_Jusho
print "\n"

print "中京大学体育学部"
print "\n"
daigaku_Jusho
print "\n"

daigaku_Jushoという関数名を、プログラムの中に書くだけで、 住所をプリントしてくれるようになります。初めのプログラムと関数を使った プログラムは実行した結果は全く同じことになりますが、 関数を使ったほうがずいぶん、簡単で見通しがよくなりますね。
欲しい結果は同じですが、プログラムの形はずいぶん違ったものになりました。 関数の働きが実現される仕組みは?  なぜ、関数がうまく働くのか、その仕組みを理解しましょう。
プログラムは、上から実行されます。そこで、まずdef...endで括られた部分が実行されますが、普通の意味での実行とちょっと違って、その部分の プログラムに名前がつけられ、登録されるということが実行されます。実際に定義の部分に書かれていることが実行されるわけではないことに注意してください。 プログラムにつけられた名前が登録され、それが使える状態になるだけだとも言ってもよいでしょう。
それでは、そこはいつ実行されるのでしょう? defendで囲まれた、定義の後に書かれている部分は、これまでのプログラムと同じです、実際に実行されます。ただ、途中に上で 定義された名前が出てくると、この名前の部分の実行はそれが定義された部分の実行になります。つまり、関数の定義の部分が呼び出され、実行されることになります。関数の名前がすでに登録されているので、そのようなことが可能になるのです。プログラムのどこが実行されているかという見方を取ると、プログラムの実行時に、関数名に遭遇すると、実行の制御が関数の定義部分に移り、その実行が終わると呼び出されたところに制御が戻るというように理解できます。実際にそうなっているのか確認してみましょう。

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

def daigaku_Jusho
    p "**** 関数が呼び出された。***\n"
    sleep(1.5)
    print "〒470-0393  豊田市貝津町床立101\n"
    print "tel:0565-45-0971\n"
    p "**** 関数の呼び出し先に戻る。***\n"
end

print "中京大学情報理工学部"
print "\n"
sleep 3
p "今から関数を呼び出します。"
sleep 5
daigaku_Jusho
print "\n"

print "中京大学現代社会学部"
print "\n"
sleep 3
p "今から関数を呼び出します。"
sleep 5
daigaku_Jusho
print "\n"

print "中京大学体育学部"
print "\n"
sleep 3
p "今から関数を呼び出します。"
sleep 5
daigaku_Jusho
print "\n"

繰り返せば、プログラムの形だけでなく、実行の制御の流れがこれまでとずいぶん違いますが、プログラムが一歩一歩実行されていくというプログラムの本質は同じです。 daigaku_Jushoという関数はプログラムの頭の部分に書かれていますが、 実際にそれが実行されるのは、プログラムの本体の中で daigaku_Jusho という部分が実行される時です。
このことをsleepとpを使って次のプログラムで確かめてみましょう。

振り返ってまとめると、関数の定義は次のように、プログラムの頭、defとendで囲んで書かれるています。」

def 関数名
  本体プログラム
end

先程のプログラムで関数名と関数の本体が何になるのかは明らかでしょう。 関数名はdaigaku_Jushoで、関数の本体は

    print "〒470-0393  豊田市貝津町床立101\n"
    print "tel:0565-45-0971\n"

になります。つまり、関数の定義は次のような形 になり、これをこの関数を呼ぶプログラムの前に書いておくことになります

def daigaku_Jusho
    print "〒470-0393  豊田市貝津町床立101\n"
    print "tel:0565-45-0971\n"
end

入力を受け取る関数

実は関数は、プログラムに名前をつけるだけではなく、次のように、入力を取れるようにすることで、もっと強力になります。

以下は、最初に挙げたプログラムと同様に、情報理工学部、生命システム工学 部、社会学部、体育学部の住所をプリントするものですが、入力を取る関数を 使っています。

def daigaku_Jusho(gakubu)
    print "中京大学" + gakubu
    print "\n"

    print "〒 470-0393  豊田市貝津町床立101\n"
    print "tel:0565-45-0971\n"
    print "\n"
end

daigaku_Jusho("情報理工学部")

daigaku_Jusho("現代社会学部")

daigaku_Jusho("体育学部")
def 関数名(引数) #関数の入力に対応する変数のことを仮引数(かりひきすう)と呼びます。
  プログラム本体
end

関数名(引数)     #関数の呼び出し部分です。この入力に対応する値のことを
                  #実引数(じつひきすう)と呼びます。

課題2-1

次のプログラムを実行すると、何が表示されるのか予想し、その表示結果を答 えなさい。次に、実際に実行してみて、予想結果と合うかどうかを確かめ、そ の結果も報告しなさい。予想と違った場合には、なぜ違ったのか、理由を説明 しなさい。

def aisatsuHiruma(name)
  print "こんにちは、", name, "さん。"
  print "\n"
end

def aisatsuYoru(name)
  print "こんばんは、", name, "さん。"
  print "\n"
end

name = "太郎"
aisatsuHiruma(name)

name = "花子"
aisatsuHiruma(name)

name = "太郎"
aisatsuYoru(name)

name = "花子"
aisatsuYoru(name)

課題2-2

次のように表示するプログラムを、上で使った(定義した) aisatsuYoru(name) と aisatsuHiruma(name) という関数を使って実現しなさい。

こんにちは、名古屋さん。
こんばんは、名古屋さん。
こんにちは、愛知さん。
こんばんは、愛知さん。

課題2-3

次のプログラムを実行すると、何が表示されるのか予想し、その表示結果を答えなさい。 次に、実際に実行してみて、予想結果と合うかどうかを確かめ、その結果も報告しなさい。 予想と違った場合には、なぜ違ったのか、理由を説明しなさい。

def aisatsuHiruma(name)
  print "こんにちは、",name,"さん。"
  print "\n"
end 

names = ["名古屋", "愛知", "中部", "大阪"]

for name in names
  aisatsuHiruma(name)
end

出力を返す関数

以下は、1からn(この場合は100)までの総和を求めるプログラムです。

n = 100
sum = 0
for i in 1..n
  sum = sum + i
end
print "合計は", sum, "です。\n"

n=100 と n=1000 と n=10000 の3つの数について和を求めるには、次のように、これを繰り返し使うことでできます。

n = 100
sum = 0
for i in 1..n
  sum = sum + i
end
print "合計は", sum, "です。\n"

n = 1000
sum = 0
for i in 1..n
  sum = sum + i
end
print "合計は", sum, "です。\n"

n = 10000
sum = 0
for i in 1..n
  sum = sum + i
end
print "合計は", sum, "です。\n"

このプログラムもやはり、足し算の部分を sowa というような名前の関数にし て使えるようにすればよいのですが、さらに次のように 入力出力 を取れるようにすると、とても強力になります。

def sowa(num)
  sum = 0
  for i in 1..num
    sum = sum + i
  end
  return(sum)
end

n = 100
print "合計は", sowa(n), "です。\n"

n = 1000
print "合計は", sowa(n), "です。\n"

n = 10000
print "合計は", sowa(n), "です。\n"

入力と出力を取る関数は、次のような形で、作ることができます。 入力に対応する変数のことを引数と呼びます。 出力される値のことを戻り値とか返り値 と呼ぶこともあります。

def 関数名(引数)  #入力に対応する値を、関数を呼び出したところから受け取ります。
  プログラム本体
  return(値)      #出力に対応する値を、関数を呼び出したところに返します。
end

このように関数を定義しておくと、関数名(引数) をプログラムの中に 書くだけで、上の sowa(num) のように、簡単に繰り返し使うことができるよう になります。 このように、プログラムの中に定義した関数の名前を書い てやると、そこで関数が実行され、結果が値として得られることになります。

課題2-4

次の関数を使ったプログラムが、どのような出力を出すか、 プログラムを一歩一歩追いかけることで予測しなさい。

def nijiKansu(m)
  kekka = m * m + 3 * m + 2
  return(kekka)
end

n = 1
print "入力値が", n, "のとき、出力値は", nijiKansu(n), "です。\n"

n = 2
print "入力値が", n, "のとき、出力値は", nijiKansu(n), "です。\n"

n = 3
print "入力値が", n, "のとき、出力値は", nijiKansu(n), "です。\n"

課題2-5

次のプログラムはn=5のとき、 数nに対して階乗(n x (n-1) x ... x 3 x 2 x 1)を計算するものです。 これをヒントに、まず、関数を使わずに、n=5, n=10, n=100の時の階乗を計算し、 次々に結果を出力するプログラムを作成しなさい。

n = 5
kekka = 1
for i in 1..n
  kekka = kekka * i
end
print "答え= ",kekka,"\n"
なお、出力結果は次のようになります:
答え= 120
答え= 3628800
答え= 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

課題2-6

次に、数nに対して階乗(n x (n-1) x ... x 3 x 2 x 1)を計算する関数、 kaijo(n)を作成し、それを使って、課題2-5と同様に、 n=5, n=10, n=100の時の階乗を計算し、値を出力するプログラムを作成しなさい。

課題2-7

数を要素とする配列を入力に取り、その総和を計算し、 値を返す関数arraySum(ar)を作成しなさい。 またこれを使って、以下のようにいろいろな配列の総和を出力するプログラムを作成しなさい。

ar = [3,5,2,7]
p arraySum(ar)
ar = [12,24,63,11,29]
p arraySum(ar)
ar = [1251,3567,9399,6241]
p arraySum(ar)
なお、出力結果の例は次です:
17       # ar = [3,5,2,7] 
139      # ar = [12,24,63,11,29] 
20458    # ar = [1251,3567,9399,6241]  

関数の中の変数名の有効範囲(スコープ)

変数名には、変数名が意味を持つ有効範囲があります。 特に、関数の内部で使われた変数名はその関数の内部でしか意味を持ちません。 その結果、定義された関数の内部と外部で同じ名前の変数を使用しても、別の変数として扱われる。ため、 同じ変数名が使われていてもお互いの間で値のやりとりは起こりません。 このような変数名の有効範囲のことを、変数のスコープ(scope)とも言います

具体的な例で見てみましょう。下の例で、関数nijouの中の変数answerの値は変化しますが、その変化は関数の外のanswerの値に何の影響も与えません。

def nijou(num)
  answer = num * num
  print "関数の中のanswerの値は", answer, "\n"
  return(answer)
end

answer = 0
print "5の二乗は", nijou(5), "\n"    #関数nijouを呼び出して実行し、戻り値を表示する。
print "答えは", answer, "です。\n"
この実行結果は以下のとおりです:
関数の中のanswerの値は25
5の二乗は25
答えは0です。

このように、関数の中の変数が外の変数に影響を持たないことで、関数を作る際に、他のところで使われている変数からの余計な影響を気にせずにつくれるようになります。 このことは、特に、大きなプログラムを分担して複数の人で作成するような場合に、重要になります。

このことから、関数の中で得られた結果は、変数を使って受け渡すことはできず、returnを使って、その関数の戻り値として受け渡すことになります。

def nijou(num)
  answer = num * num
  print "関数の中のanswerの値は", answer, "\n"
  return(answer)     #変数answerの値を戻り値として出力する。
end

answer = nijou(5)    #関数nijouを呼び出して実行し、戻り値を代入する。
print "答えは", answer, "です。\n"
この実行結果は以下のとおりです:
関数の中のanswerの値は25
答えは25です。

課題3-1

次のプログラムを実行するとどのように表示されるか、プログラムを一歩一歩追いかけて、予測して報告しなさい。 その後、実際に実行して、予測と合ったかどうか報告しなさい。 予測と合わなかったら、もう一度上の解説をよく読んで、なぜそのように表示されるのか考え、説明しなさい。

def kansu
  a = 10
  b = 20
  c = 30
  return(b)
end

a = 0
b = 0
c = 0

c = kansu    #関数を呼び出して実行し、戻り値を代入する。

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

[ヒント]

return文によって関数から返されるのは、変数ではなく、変数の持つ値です。 このプログラムでは、bという変数の持つが、関数から返却されます。

def kansu
  a = 10
  b = 20
  c = 30
  return(b)    #変数bの値を出力する。
end

関数の内部では、変数bの値は20ですので、この関数からは20という数が返却されます。 その後、関数から出力された20という値は、関数の外部の変数cに代入されます。

c = kansu    #関数を呼び出して実行し、戻り値の20を代入する。

この変数cは、関数の定義の外側で使用される変数ですので、関数内部の変数cとは別の変数です。

複数の値を受け取る関数

関数は二つ以上の引数を受け取ることもできます。 複数の値を受け取る関数を定義する場合、次のように仮引数の変数名をカンマで区切って指定します。

def 関数名(変数1, 変数2, ... , 変数n)
  プログラム
end

この関数を呼び出して使用するときは、定義部分と同じように、実引数の値(もしくは変数名)をカンマで区切って指定する必要があります。

関数名(値1, 値2, ... , 値n)

一つ目の引数(変数1、値1)を第一引数と呼び、二つ目の引数(変数2、値2)を第二引数と呼びます。 関数が実行されると、第一引数の変数1には値1が代入され、第二引数の変数2には値2が代入されます。

課題3-2

次のプログラムを実行するとどのように表示されるか、プログラムを一歩一歩追いかけて、予測して報告しなさい。 その後、実際に実行して、予測と合ったかどうか報告しなさい。 予測と合わなかったら、なぜそのように表示されるのか考え、説明しなさい。

def sum_of(n1, n2)
  sum = n1 + n2
  return(sum)
end

print "結果は", sum_of(10, 20), "です。\n"

課題3-3

次のプログラムを実行するとどのように表示されるか、プログラムを一歩一歩追いかけて、予測して報告しなさい。 その後、実際に実行して、予測と合ったかどうか報告しなさい。 予測と合わなかったら、なぜそのように表示されるのか考え、説明しなさい。

def show_values(b, a)
  print "aの値は", a, "です。\n"
  print "bの値は", b, "です。\n"
end

a = 10
b = 20
print "aの値は", a, "です。\n"
print "bの値は", b, "です。\n"

show_values(a, b)    #関数show_valuesを呼び出して実行する。

[課題3-3のヒント]

変数にはスコープ(有効範囲)があるため、関数の内部と外部では、変数名に関連はありません。 このルールは引数として使用される変数にも当てはまります。 下の図の仮引数の変数(ba)と実引数の値(1020)とを結びつけるのは、引数の書かれている順序、つまり「何番目の引数か」ということだけです。

def show_values(b, a)  #bとaは仮引数。
  ...
end

...
show_values(10, 20)    #10と20が実引数。

この場合、第一引数のbには10が代入されて、第二引数のaには20が代入されることになります。

課題3-4

次の文字列の中にに何匹「しろねこ」がいるのか数えて、その数を返すプログラムをshironeko_count(str)という関数をまず作成し、それを使って数えなさい。

def shironeko_count(str)
  ...#以下を作る。
  ...
end

str = "しろねこ みけねこ しろねこ くろねこ しろねこ みけねこ くろねこ しろねこ みけねこ"

print "しろねこは", shironeko_count(str), "匹います。\n"

結果は次のように表示しなさい。

しろねこは4匹います。

課題3-5

今度は、文字列とねこの種類を渡すと、そのねこの数を文字列の中から数えるneko_count(str,type)という関数を作り、それを使って、しろねこの数を数えるプログラムを作成しなさい。

def neko_count(str, type)
  ...#以下を作る。
  ...
end

str = "しろねこ みけねこ しろねこ くろねこ しろねこ みけねこ くろねこ しろねこ みけねこ"
neko = "しろねこ"

print neko, "は", neko_count(str, neko), "匹います。\n"

課題3-6

課題3-5で作った関数neko_count(str,type)を使って、次の文字列の中から、「しろねこ」だけでなく、「くろねこ」と「みけねこ」の数も数えるプログラムを作成しなさい。

str = "しろねこ みけねこ しろねこ くろねこ しろねこ みけねこ くろねこ しろねこ みけねこ"
しろねこは4匹います。
くろねこは2匹います。
みけねこは3匹います。

課題3-7

次のように要素が数、または配列からなる配列がある。

ary = [3, 51, 2, [22, 7, 4, 8], 3, 5, [12, 13], 98]

このような配列を引数にとり、すべての数の合計を計算するプログラムを作りなさい。

この実行結果は以下のとおりです:

228

[課題3-7のヒント]

君たちの中には、

 [3, 51, 2, [22, 7, 4, 8], 3, 5, [12, 13], 98]
において、配列の中のカッコ([ ]) を外してしまえば、
 [3, 51, 2, 22, 7, 4, 8, 3, 5, 12, 13, 98]
となるから、前に作ったarraySumが使えるのでは、と思った人がいるかもしれない。

しかし、この方法は使えない。 もしも入力が "[3, 51, 2, [22, 7, 4, 8], 3, 5, [12, 13], 98]" というような「文字列」なら、文字としてのカッコを消す方法はある。 しかしこの問題の場合、カッコは「文字」ではなく「配列がそこにあることを示すマーク」 として表示されたものに過ぎない(言い換えれば、計算機の中では、文字列として表現されていない)。 だから、このような方法は使えないのである。
ここではプログラミングとしての「正攻法」について解説する。

基本は、課題2-7で作った arraySumである。 ただarraySumは要素が数の場合だけを想定し、その和を求めるものであった。
ここでは、要素が数か配列かを分けて考える必要がある。 なお、配列の要素が配列か数かは、次のように.class.name (または .class だけでもできる...)を使って判定できる。

ary = [3, 1]
p ary.class.name
num = 234
p num.class.name

結果は

"Array"
"Fixnum"

となる。

なお、要素が配列の場合、arraySumを使えば、その配列の要素の和が得られることは言うまでもないだろう。


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