[目次] [第6回] [第8回]

Ruby勉強会(第7回)

練習6-1、6-2の回答例

練習6-1については、前回の練習5-2 の回答例であるex5-3.rbを分割して、次の3つのファイルを回答例として おいておく。

なお、student.rbの中の冒頭にコメントを入れておくが、 このようにそのファイルを用いる際にそもそも依存しているファイルがあれば、 そのファイルにおいてrequireを指定する方が良い(そのために、 requireは同じファイルを一度だけ読み込むという定義に されている)。また、Timeクラスの代わりにDateクラスを用いたものを human2.rbとしておいておく。

練習6-2については、メソッドopenの引数にファイル名だけでなく URL(URI)を与えられるようにするためには、「Rubyリファレンスマニュアル」の 「添付ライブラリ」の項の中の「ネットワーク」にopen-uriというもの があり、これが最も簡単に拡張してくれる。これを用いた例を ex6-2.rb としておいておく。

簡易電卓を作成する

これまで作成・実行してきたプログラムはあくまでも例のための例という 位置づけであった。この勉強会の締めくくりとして、多少は実用的な機能を 備えたプログラムの作成を試みる。今回はその基本機能の実現について 扱い、次回以降は GUI (Graphical User Interface)の実装について扱う。

目標とするものは簡易的な電卓とする。MS-Windows などのOSや携帯電話など にも電卓機能はあるが、それらは Rubyのようなメモリのある限り長い桁の計算を する機能はなく、10桁程度の有効数字の計算しかできない。そこで、Rubyの 無限精度整数の演算機能を簡単に使える電卓を構築したいと思う。 irbを用いれば、ある程度の計算を対話的にできるが、あれは あくまでも Ruby のプログラムの実行のためのものであり、コマンド プロンプトを用いるので、GUIを備え、計算に特化したものを作成する。

数式通り方式と逆ポーランド方式

現在の電卓の多くはいわゆる「数式通り」の入力方式をとっているとされる。 しかし、正確に数式通りの入力を実現しているのは画面上に数式を表示する機能を 備えた一部の関数電卓のみで、多くの電卓は以下のように、厳密には数式通り といえない面を持っている。

これは、キー入力のみで計算を指示するという制約の元に繁雑なキー操作を 避ける意味もあるかも知れないが、特に後者は「処理を実現が面倒であり、 計算途中に表示されている値が何を示しているかわかりにくい」ということなど が理由になっていると思われる。

それに対して、従来より高度な計算機能を備えた電卓の一部に 「逆ポーランド記法」に従った入力方式をとるものがある。 これは、演算対象となる値をまず入力し、演算の指示を後で入れるものである。 例えば、1 + 2は「1」、「2」、「+」の順で入力して、「+」を 入力した時点で演算を行う。この方式はスタックを用いることで容易に実現でき、 また画面に表示されている値は必ずスタックトップであるとい明解さがある。 また、乗除優先のような問題も原理的に存在しなくなる(括弧も不要となる)。

ここではこの逆ポーランド方式の電卓の構築を目指す。

スタックと逆ポーランド方式

逆ポーランド方式はスタックを用いて次のようなアルゴリズムで簡単に実現 できる。

  1. 数値が入力されたら、それをスタックに入れる(プッシュする)。
  2. 演算子が入力されたら、それが示す演算に必要な個数の数値をスタックから 取り出し(ポップし)、それらを用いて演算した結果の値をスタックに 入れる(プッシュする)。
  3. 入力に対応する各処理が終れば、スタックトップを表示し、 次の入力を待つ。

入力と表示については最終的に GUI を作成するので、後回しにすると して、まずは上の基本的な処理を Rubyで実現する。

Rubyにおけるスタック

スタックとは後入れ先出し(LIFO: Last-In, First-Out)とも呼ばれる データ構造としては最も基本的なものの一つである。データを入れる操作を プッシュ(push)と呼び、取り出す操作をポップ(pop)と呼ぶ。ポップされる時、 取り出されるデータは、直前、つまり最後にプッシュされたものである。 言い替えるとデータを入力した順とは逆順にデータを取り出すことになる。

多くのオブジェクト指向プログラミング言語ではこのスタックを実現した クラスが準備されているが、Rubyでは少し変わっていて、配列(Array)に スタックとしてデータを操作する機能も準備されている。

変数aに配列が入っているとすると、
a.push(データ)
を実行すると、その配列の末尾にデータが追加され、
a.pop
を実行すると配列の末尾のデータが配列から取り除かれ、それがこの式の 値となる。

練習7-1

irbを用いて、配列にデータをプッシュ、ポップして確かめてみよ。 なお、最初にデータが一つも入ってない空の配列を準備する必要があるが、 それは、
a = []
とすれば良い。

クラスCalcを作成する

配列をスタックとして用いることにして、これを使って電卓の演算機能を 備えたクラスを定義する。

まずインスタンス生成時の初期化メソッド(initialize)では、 インスタンス変数「@stack」に空の配列を代入し、データが空っぽの 状態から始めることにする。

次に数値が入力された時に実行すべきメソッドpushを定義する。 これは実際には@stackにそのままpushすれば良い。

演算子についてはまず加算について考える。加算を行うメソッドをここでは addとする。そこでは、上のアルゴリズムに従うとまず値を2つポップして、 それから計算(加算)し、結果をプッシュするとある。従って、

def add
    x = @stack.pop
    y = @stack.pop
    z = x + y
    @stack.push(z)
end

とすれば良い。あとは、画面に表示するためにスタックトップの値を 取り出す必要があるが、その時スタックからデータを取り除くと続けての計算が できなくなるので、popではなく
@stack.last
を用いると良い(最後の要素がスタックのトップになる)。このメソッドを 例えば、topとして準備する。

練習7-2

上の説明や例に従って、四則演算のそれぞれのメソッドも備えたクラス Calcを作成せよ。作成後はそのインスタンスの生成をして、 値をpushした後に演算のメソッドを呼ぶなどのテストをすること。


[目次] [第6回] [第8回]

by Tetsuo Sakaguchi
$Date: 2008/01/25 07:01:00 $(UTC)