ベクトル電卓

Ruby には Fixnum, Float, Array, String など多くの組み込みクラスがあります。ところが驚いたことにその組み込みクラスの演算子をユーザが再定義することができるのです。この機能を利用して Array クラスの + 演算子と * 演算子を再定義(オーバーライド)して、irb をベクトル電卓に変身させてみましょう。

まず、irbをベクトル電卓に変身させる次のスクリプトを vector.rb という名前で作成します。

class Array
  def isvector
    if self.type == Array and self[0].type != Array
      true
    else
      false
    end
  end

  def ismatrix
    if self.type == Array and self[0].type == Array
      true
    else
      false
    end
  end

  def +(other)
    if self.isvector
      temp = other.dup
      self.collect {|x| x + temp.shift }
    elsif self.ismatrix
      temp = other.dup
      self.collect do |row|
        temp_row = temp.shift
        row.collect {|x| x + temp_row.shift}
      end
    end
  end

  def -(other)
    if self.isvector
      temp = other.dup
      self.collect {|x| x - temp.shift }
    elsif self.ismatrix
      temp = other.dup
      self.collect do |row|
        temp_row = temp.shift
        row.collect {|x| x - temp_row.shift}
      end
    end
  end

  def *(other)
    if other.type != Array
      if self.isvector
        self.collect {|x| x * other }
      elsif self.ismatrix
        self.collect do |raw|
          raw.collect {|x| x * other }
        end
      end
    elsif other.isvector
      if self.isvector
        result = 0.0
        self.each_index {|i| result += self[i] * other[i]}
        result
      elsif self.ismatrix
        result = []
        self.each do |row|
          sum = 0.0
          row.each_index {|i| sum += row[i] * other[i]}
          result.push( sum )
        end
        result
      end
    elsif other.ismatrix
      m = self.size
      p = self[0].size
      n = other.size
      result = []
      for i in 0...m
        row = []
        for j in 0...n
          row.push(0)
        end
        result.push(row)
      end
      for i in 0...m
        for j in 0...n
          sum = 0.0
          for k in 0...p
            sum += self[i][k] * other[k][j]
          end
          result[i][j] = sum
        end
      end
      result
    end
  end
end

ちょっと長いスクリプトですが、Array クラスに isvector と ismatrix の二つのメソッドを追加し、それを利用して、+ 演算子、- 演算子、* 演算子で配列を利用したベクトル演算ができるように再定義を行っているだけです。Array オブジェクトである配列をベクトルのオブジェクトとして使い、配列の配列を行列のオブジェクトとして使っています。isvector ではオブジェクト a のタイプが Array 型で、a[0] のタイプが Array 型ではないとき a がベクトルであると判断して true を返します。また a.type も a[0].type も Array 型の時には a が行列であると判断して ismatrix の戻り値が true になります。

vector.rb の使い方は次のようになります。まずコンソールから irb と入力して irb を起動した後、require "vector.rb" を irb のプロンプトから実行します。これで、準備完了です。この操作で、Array クラスの演算子 + と * がベクトル演算の演算子に再定義されてしまうので、次のように irb をベクトル電卓として使うことができます。

$ irb 
irb(main):001:0> require "vector.rb"
true
irb(main):002:0> a = [1, 2]
[1, 2]
irb(main):003:0> a + a
[2, 4]
irb(main):004:0> a * 2
[2, 4]
irb(main):005:0> a * a
5.0
irb(main):006:0> b = [[1, 0], [0, 1]]
[[1, 0], [0, 1]]
irb(main):007:0> b * a
[1.0, 2.0]
irb(main):008:0> c = [[1, 2], [3, 4]]
[[1, 2], [3, 4]]
irb(main):009:0> b * c
[[1.0, 2.0], [3.0, 4.0]]
irb(main):010:0> ( b * c ) * ( a * 2 )
[10.0, 22.0]

最後の行の例でも分かるように括弧を使った計算もちゃんとやってくれます。他のスクリプト言語であればパーサーから設計しないといけないところです。このようなヤドカリのような芸当をも簡単にやれてしまうのが Ruby という言語の不思議な所です。

Ruby のもう一つの面白いところはクラスを定義する文の class 文が実行可能な文であると言うことです。したがって、スクリプト実行中にも動的にクラスの性質の変更ができてしまいます。例えば上のベクトル電卓の場合ですが、irb を起動しただけだと次のように配列 a と 配列 b の + 演算子の結果は配列の結合になってしまいます。

$ irb
irb(main):001:0> a = [1, 2]
[1, 2]
irb(main):002:0> b = [3, 4]
[3, 4]
irb(main):003:0> a + b
[1, 2, 3, 4]

ところがその次に require 文で + 演算子と * 演算子の再定義を実行すると、a + b が今度はベクトルの和を返すようになります。

irb(main):004:0> require "vector.rb"
true
irb(main):005:0> a + b
[4, 6]

このようにスクリプトを走らせたまま、クラスの仕様を変更することができるので、プログラムの開発を柔軟に行うことができます。(2001.11.1)