情報科学概論
2001.5.29



  1. 本日の作業内容

    1. メソッド定義
    2. ローカル変数
    3. ブロック付きメソッド
    4. 教科書の例題と課題の間のギャップ?について
      1. アルゴリズム
      2. 実現方法
    5. 本日の実習
    6. 宿題


    7. メソッド定義

      教科書p.39

      本日の学習範囲は先週までと異なりメソッドを定義することを中心としている。1.12および1.13節の目的は、プログラミングを実際に行う場合に生じる問題に準備するためであり、現在まで学習した範囲内でスクリプトを書く上ではまだ必要ない話である。具体的に言うと、長くて複雑な処理プログラムを作成する場合、その中のここの処理やメソッドをグループ化して個別に読み出す方が、一つの流れとしてスクリプトを作るよりも読みやすく修正しやすいものになるからである。

      例を挙げると、計算機における三角関数 sin x の値は、実は

      のようにして求められるのだが、それを使う場所ごとに

      x - x**3/3/2 + x**5/5/4/3/2 - x**7/7/6/5/4/3/2 +

      のように計算するのは入力ミスの確率も増えるし、面倒である。そこで、関数として sin x と定義されていると必要に応じて sin x とするだけで良いのでプログラムを組む上で楽になる。 sin x などは通常の言語であれば大抵組み込まれているので意識することはないが、例えば、階乗の計算などは標準では用意されていない場合が多い。そこで、

      def fact(n)
        return 1 if n == 0
        f = 1
        while n > 0
          f *= n
          n -= 1
        end
        return f
      end

      のようにスクリプトのどこかで定義しておけば、先ほどの例だと

      x - x**3/fact(3) + x**5/fact(5) - x**7/fact(7) +

      のように書ける。

      今のところは無理して使用する必要はないが、スクリプトの処理構造の流れを中断して見通しが悪くなるような場合には、別の場所で

      def

      end

      で囲んで関数的な処理を定義しておくと良いかもしれない。

      ところで、p.40の中央にある和を求めるスクリプトであるが、再起的定義を使用した非常に高尚な方法を使用している。方法として知っておく程度でよいが、どのように動作しているのか、わかるだろうか?ここの例では、sum(10)を求めようとしているので、n=10がまずifで試される。elseの方に進むので処理は10 + sum(9)となるが、sum(9)はまだ計算していない。そこで、自分自身の定義に戻ってsum(9)を計算するが、そうすると9 + sum(8)となり、また、定義に戻って計算する必要がある。 結局、 n = 0 まで延々と進み、処理が終わる。動きを確かめるには自分でpの命令を入れてみるのがよい。実際のところは、授業中に簡単に解説する予定である。

      また、これについては便利な計算方法があるのを習ったことがあるだろう。逸話として今も語り継がれているのは、後に天才数学者となったガウスが幼少の頃の話である。

      10歳くらいの時に学校で教師が1から50までの整数の和を求める課題を出した。周りの子供が順番に足し算をしていく中で、ガウス少年はしばらく考え込んでいたが、やがて「出来た!」と言って正解を答え教師を驚かせた。方法については、有名なので紹介するまでも無いが、彼は以下のような計算を行って暗算で答を求めた。

      1+2+3++50
      +)50+49+48++1
      ---------------------------------
      51+51+51++51= 51 x 50
        ∴ 1 + 2 + 3 + + 50 = 51 x 50 / 2 = 2550 / 2 = 1275

      これは、処理を行うのにプログラミングするということが有効な例と言えないだろうか?

    8. ローカル変数

      教科書p.40

      今まで、例題や課題で使用してきた変数はほぼ全てがローカル変数と呼ばれるものであった。前節で出てきたdef...endで定義するような範囲をスコープと呼ぶが、その範囲内で定義されたり代入されたりした値はそのスコープの範囲内だけで有効であるのが、ローカル変数である。しかし、グローバル変数を使う必要は現状ではないので、気にしなくて良い。

    9. ブロック付きメソッド

      教科書p.41

      教科書のp.42にある下側の例と図1.11を比較すると以下のような手順で処理が進むことが分かる。

      1. bar(1,3)を見て実引数が1と3であることが分かる
      2. 仮引数x、yに1、3を代入するようにdef文の中の処理を行う
      3. def文の中のyieldの処理の中身がdef文の中にないことがわかる
      4. yieldの処理は{|a, b| a / b}と指定されているので、それに従いx + 6/y = 1 + 6/3 = 3 を計算する

      なかなか難解な表現の続く記述であるが、このような処理が必要な場合もあると言うことである。

      p.43からは繰り返しを行うメソッドであるイテレータについて、いくつか例がある。eachに関して例を実行してみて感覚をつかむのが望ましい。注意する点は教科書の中にも何度か出ているように、配列の中身を見るときはpというメソッドにより行うことである。例えば、p.44の一番上にある例を実行してみるときに、

      value = [2, 3, 4].collect{|x| x * x}
      puts value
      p value
      

      として、putsによる出力とpによる出力結果の違いを見るとよく分かる。

    10. 教科書の例題と課題の間のギャップ?について

      教科書には例題がたくさん載っている。それらを自分で実行していく過程でだんだんとRubyへの理解が深まるという予測の元に教科書が書かれているからである。現在は入門の章なので、教科書の例題は原則として非常に簡潔なものであり、そこで紹介されている機能、メソッドだけを用いて動作を検証するようになっている。

      ところが、教科書を学習していても、宿題のスクリプトが難しく感じることが多いようである。そこに、ギャップがあるとすると、それはRubyの理解の問題ではなく「プログラミング」自身の問題であろう。すなわち、自分の実行したいことを計算機に命令することに慣れていないため、計算機が理解できる単純な命令の形で自分の希望を表現できないことが原因と推測される。以下にその点について簡単に補足する。

      1. アルゴリズム

        アルゴリズムとはプログラミングにおいては実行させる処理を順番に記述したものである。図示したものをフローチャートなどとも呼ぶが、とにかく、手順を記したものである。例えば、人におつかいをたのむ場合を考えてみる。買ってきて欲しいのは「アイスクリーム」とする。自分の希望を紙に書いて渡すことにする。その際に、「バニラ味とチョコレート味の二つを含むカップタイプで6個入り500円以内のもの」が希望であるとすると、そのように紙に書いて渡せばよい。(これでも通常の場合よりもかなり詳しい指令書である。)ところが、それを計算機でも理解できるような表現に変えるとどのようになるであろうか?

        • 条件1:カップタイプであるか?
        • 条件2:6個(以上)入っているか?
        • 条件3:バニラ味は入っているか?
        • 条件4:チョコレート味は入っているか?
        • 条件5:価格は500円以下か?

        上記の判定をifで行って全てが真であれば購入となる。(もちろん、実際の買い物では条件を満足するものが複数ある場合があり、さらに判断基準が必要となるかもしれない。また、条件を満足するものがない場合にどのように振る舞うか、あらかじめ記述しておかなければならない。)

        このように自分のやりたいことを客観的な記述に変更することが重要であり、それがプログラムを書き始めたときに誰もが悩む点である。実際にかかれているプログラムがあればそれを読んで理解するのも良いし、片っ端から書いていって上手く動作するか試行錯誤するのも良い。宿題の場合だと、入力条件がいろいろと変わるものが多いので、一見まともに書けたように見えて条件を変更するとエラーが出る場合も想定される。自分で実際に試してみてエラーがないか見つける工夫も必要である。

      2. 実現方法

        上記のようなアルゴリズム的なものが頭の中に出来たり、紙に書いてみて確認できれば、次の段階がプログラミングである。その時に、今までに学習した内容と若干の補足説明やヒントなどの非常に初歩的な限られた内容から処理を組み立てる作業もなかなか難しい点である。これには、授業中に前回までの宿題のことを考えていて、その日の作業をきちんとこなしていなければよけいに難しくなることも加わるので注意が必要である。

        宿題はその日の教科書の内容と実習作業を通して学習したことを応用する内容とするようにしている。その日の作業について自分で作業していないと、宿題は当然難しくなる。課題の提出が遅れるのはやむを得ないこともあるが、授業中はその日の内容について実習することに力を入れないと、今後はさらに難しくなるであろう。
        では、実現するためのやり方にこつがあるか、というと単純には回答しにくい。多くの人が困った5/8の元号の課題であるが、それについては、print文という習っていない要素もあるにはあったが、中身自体はひたすらif文で条件分岐をするだけであった。ただし、条件分岐の数がいきなり多いものだったので、正確性や根気などの学力の基礎となる部分が露呈したのは興味深い。以前にも言ったように、その宿題は数学で言う計算問題に近いものであり、一つずつ正確にきちんとやれば必ず出来るものであるが、その辺りが苦手な人は苦労したようである。また、プログラミング技術以前に、実際の西暦と元号の対応を知らないと出来ないのも当然である。明治が何年まであったのか、大正はどうか、昭和についてもどうか、そういうものを自分で調べる能力も実は問うていたのである。

        同様に5/15の課題では「弧度法」に対する理解を調べている。大学の数学では三角関数の引数としては全てラジアンを単位として計算を行う。そのような基本的なこと、すなわち、やはりプログラミング以前に数学の知識を確認するのも目的であった。この課題などは自分で簡単に手計算で出来る程度の内容である。少なくとも、出てきた答を自分で計算したものと比較してあっているかどうか、くらいは確認できないといけない。

        結局、プログラムを実現するためには、プログラミング以前に自分の手で紙と鉛筆で実現できるか、まず、確かめてから始めるのがよい。もちろん、本格的なプログラミングであれば手では計算できないから計算機にやらせるわけで、確認できるのは非常に限られた極限の状態だけになるが、それでも計算機に計算させた場合にも何らかの照合作業が必要なことは覚えておくべきである。

        照合作業の助けとなるのが計算途中の値の表示である。スクリプトの随所に今その変数や配列がどういう値を持っているのか、pやputsなどを挿入して自分の思うように動作しているか確認するのは大事である。特に、コマンドラインから読み込んで配列にする際など、ちゃんと値が入っていると思っていてもnilが返ってくることが初心者には多い。なるべく順を追って自分が手で計算した変数や配列の値と処理があっているか、検算しながら行うと、間違いの原因を見つけやすい。これについては授業中に例を示して説明する。

        さて、前回から配列が登場したので、プログラムとしてはようやく必要な素材がそろったと言うところである。配列を利用して何が出来るのか、処理をどうやって実現するのか、その辺りを前回の宿題で考えてもらった。ただし、前回の宿題は配列は出ては来るが、値を変数に代入する際に便利であるから、と言う程度で済むレベルであった。計算機の特徴は人間がとても扱えないような膨大な数の計算や処理が得意なことである。配列は、非常に重要な概念なので今後ともしっかり勉強すること。

        結局、ここのまとめとしては、自分で手で計算や処理が出来ることであればやってみる。それをアルゴリズムを考えながら計算機が理解できる表現に直していく。という操作を練習することにつきる。実習作業や宿題はその理解を助けるような題材が選ばれているので、どこがポイントなのか考えてみよう。

        このところの宿題はスクリプトを書くと数行で終わるものが中心である。だからといっていきなりスクリプトを書く、というよりも、まずは紙と鉛筆でどういう流れにより実現できるかを考えてみることから始めよう。優秀なプログラマでもまずは全体を見通してどのような処理にするか、じっくり考えてから書き始めるのだから。
    11. 本日の実習

      作業1
      教科書にある、各種スクリプトの例などを自分で確認すること。

      作業2
      教科書p.40の和のスクリプトに対して、ガウスが行った手法によるスクリプトを書いて比較して見よ。

    12. 宿題

      授業の終わり頃に次回までに提出する課題を発表するのでアナウンスに注意すること。また、発表されたら課題を表示するためには、一度このページを再読み込みする必要があるのでNetscapeのボタンをクリックする。そうしないと、課題のページは表示されない。


    目次ページに戻る