クラスの親子関係
Rubyのクラスには親子関係があります。 あるクラスの子をサブクラスといいます。 サブクラスは複数作ることができますが、親クラス(スーパークラス)を子から作ることはできません。 したがって、この親子関係はツリー状になります。
すべてのクラスの祖先はBasicObjectというクラスです。
あるクラスがどういう祖先を持っているかはancestors
メソッドで分かります。
p String.ancestors
これを実行すると
[String, Comparable, Object, Kernel, BasicObject]
と表示されます。 左から右へ「子から親」の順に並んでいます。 このうちComparableとKernelはクラスではなくモジュールです。 モジュールについては別の記事で説明します。 クラスだけの親子で言えば
String => Object => BasicObject
ということになります。 以下では子クラスを「サブクラス」、親クラスを「スーパークラス」と呼びます。
サブクラスはスーパークラスの性質、例えばメソッド、インスタンス変数を受け継ぎます。
- サブクラスで使えるメソッドは、「スーパークラスのメソッド」「サブクラス独自のメソッド」
- サブクラスのメソッドでは「スーパークラスのメソッドで定義されたインスタンス変数」「サブクラスのメソッドで定義されたインスタンス変数」を参照できる
親から子になるにつれ、より個別的、具体的なクラスになっていきます。
サブクラスの定義
サブクラスの定義は<
をクラス名の後につけ、さらにスーパークラスを置きます。
class A < B end
この例ではAがBのサブクラス、BがAのスーパークラスです。 BはRubyが既に定義しているクラスでも、ユーザが新たに定義したクラスでも構いません。
サンプルデータの作成
ここではサブクラスの例として統計のサンプルデータの集合を表すクラスを作ってみましょう。
今回は都道府県別人口のデータを処理するクラスを作ってみます。 データは独立行政法人統計センターのSSDSE(教育用標準データセット)から取ってきました。
2022/9/22の時点では「SSDSE-E-2022v2」(2022年第2版)が最新です。
データの中から都道府県名と人口をセットにして、ヒア・ドキュメントの文字列にしました。
北海道 5224614 青森県 1237984 ... ...
都道府県と人口の間はタブで区切られています。 このデータをハッシュにして取り出すには
- 改行を区切りとして各行を要素とする配列にする
- 各要素をタブを区切りとして、都道府県名と人口の2要素の配列にする
- その2要素の配列を文字列からシンボル、整数に変更する
- 配列をハッシュに変換する
それぞれの項目はメソッドで実現できます。 全体として連続したメソッドになりますが、これを「メソッド・チェーン」ということもあります。 ここまでをプログラムにしましょう。 少し長くなりますが、全都道府県の人口データもプログラムリストに入れておきます。
d = <<EOS 北海道 5224614 青森県 1237984 岩手県 1210534 宮城県 2301996 秋田県 959502 山形県 1068027 福島県 1833152 茨城県 2867009 栃木県 1933146 群馬県 1939110 埼玉県 7344765 千葉県 6284480 東京都 14047594 神奈川県 9237337 新潟県 2201272 富山県 1034814 石川県 1132526 福井県 766863 山梨県 809974 長野県 2048011 岐阜県 1978742 静岡県 3633202 愛知県 7542415 三重県 1770254 滋賀県 1413610 京都府 2578087 大阪府 8837685 兵庫県 5465002 奈良県 1324473 和歌山県 922584 鳥取県 553407 島根県 671126 岡山県 1888432 広島県 2799702 山口県 1342059 徳島県 719559 香川県 950244 愛媛県 1334841 高知県 691527 福岡県 5135214 佐賀県 811442 長崎県 1312317 熊本県 1738301 大分県 1123852 宮崎県 1069576 鹿児島県 1588256 沖縄県 1467480 EOS d = d.split(/\n/)\ .map{|s| s.split(/\t/)}\ .map{|a| [a[0].to_sym, a[1].to_i]}\ .to_h
最後から2〜4行目の最後にバックスラッシュがあります。 これは改行を取り消すためのもので、次の行も同じ一行にくっつけるという意味です。 Rubyでは改行が空白よりも強い区切り(文の区切り)であるので、これをつけておきます。 ただし、これは一般論であり、上記のようなメソッドチェーンではバックスラッシュを取り去ってもRubyは同じように解釈してくれます。 好みの問題ですが、バックスラッシュをつけるほうが文のつながりを意識できて良いと思います。 to_hメソッドは配列をハッシュに変換します。 変換するには配列の内容がハッシュに対応するものでなければなりません。 詳細はRubyのドキュメントのArrayを参照してください。
結果としてdは
d = {:北海道=>5224614, :青森県=>1237984, ... ... ... , :沖縄県=>1467480}
となります。
Statクラス
ハッシュのサブクラスStatを統計処理のために作ります。 Statの中身はハッシュと同じですが、次のようなメソッドを作ります。
- 値の検索(キーと値の組からなるハッシュを返す)
- 最大の値をもつ組の検索
- 最小の値をもつ組の検索
- 平均値の算出
- 昇順ソート
- 並びを逆順にする
最初の3つは検索結果をStatオブジェクトで返します。 平均値は小数点以下第1位までに丸めた実数を返します。 最後の2つは並び替えられたStatオブジェクトを返します。
これらの機能はハッシュにはないものです。
class Stat < Hash def initialize h={} super() update(h) end def find_by_value(v) Stat.new(select{|key, value| value == v}) end def max m = map{|k,v| v}.max find_by_value(m) end def min m = map{|k,v| v}.min find_by_value(m) end def average (map{|k,v| v}.sum.to_f/size).round(1) end def sort Stat.new(super{|a,b| a[1]<=>b[1]}.to_h) end def reverse Stat.new(to_a.reverse.to_h) end end
このプログラムで注意真ければならないのはself
という特別な変数(疑似変数)です。
selfは「その」オブジェクト自身を表します。
def abc self.clear clear end
メソッドabcの定義の中のselfとは、次のようなオブジェクトです。
例えばそのオブジェクトが変数xx
に代入されていて、xx.abc
が実行されたとき、self
は変数xx
の指すオブジェクトになります。
同じクラスの別のオブジェクトが変数yy
に代入されていて、yy.abc
が実行されたときはself
はyy
の指すオブジェクトになります。
ですので、selfは固定されたオブジェクトではなく、メソッドabc
が呼ばれたときのabc
が属するオブジェクトになります。
このように、メソッドにはかならずそのメソッドとセットになったオブジェクトがあり、そのオブジェクトをメソッドのレシーバーとも言います。
ですから、「selfはメソッドabcのレシーバーを表す」とも言えます。
1行目はselfを空のハッシュにします。 2行目はレシーバーが書かれていません。 メソッド名だけです。 「メソッド名だけでメソッドが呼ばれたときのレシーバーは、selfとする」という約束があります。 ですから2行目と1行目は同じ結果をもたらします。
それでは、Statクラスの定義を説明しましょう。 クラス定義の中にはレシーバが省略されたメソッドが多数出てきますので注意してください。
- StatはHashのサブクラス(
class Stat < Hash
の部分がそれを表す) - インスタンスを生成する
Stat.new
が呼ばれたとき、initializeメソッドが初期化をする。h={}
というパラメータがあるので、newメソッドには引数をつけることができる。 引数が与えられなかったときのデフォルト値は{}
、つまり空のハッシュ。super()
はこのメソッドのスーパークラスにおける同名のメソッドを呼び出す。 つまりHashのinitializeメソッドが呼び出される。 したがって、super()は空のハッシュ・インスタンスを生成、初期化する。 updateはレシーバが省略されたメソッドなので、selfをレシーバとして実行される。 updateはHashのメソッドで、引数を破壊的に付け加える。 以上から、Stat.newにハッシュが引数で与えられたときは、そのハッシュを中身とするStatオブジェクトが生成される。 引数がなければ空のStatオブジェクトが生成される - find_by_valueにはパラメータvがある。 vはハッシュの値(都道府県データでは人口を表す整数)のどれかであることが期待されている。 その値をもつキーと値の対からなるStatオブジェクトを返す。 selectメソッドはブロックの値が真になるような対だけからなるハッシュを返す。
- maxは、まず値だけの配列をmapメソッドで作り、その最大値を(配列の)maxオブジェクトで取り出す。 最後にfind_by_valueでStatオブジェクトを返す。
- minは同じことを最小値について行う
- averageでは、Statの値だけからなる配列を作り、その合計を計算し、実数に変換、要素数で割って平均を求め、最後にroundで小数点以下一位に丸める
- sortでは、Hashのsortメソッドをsuperで呼び出し、大小をブロックで評価してソートする。
- reverseでは、ハッシュを配列に変換(各「キーと値」はその2つを要素とする配列に変換される)、reverseメソッドで逆順にし、ハッシュに戻す
説明は長くなってしまいましたが、これはメソッドチェーンでたくさんのことをコンパクトにプログラムしたからです。 逆にいえば、「Rubyでは短いプログラムで多くのことを実行できる」ということになります。
プログラムの実行
データの定義、Statクラスの定義に続けて、次のプログラムを書き加えます。
s = Stat.new(d) p s.find_by_value(7344765) p s.find_by_value(0) p s.max p s.min p s.average p s.sort p s.sort.reverse
実行してみましょう。
{:埼玉県=>7344765} {} {:東京都=>14047594} {:鳥取県=>553407} 2683959.6 {:鳥取県=>553407, :島根県=>671126, ... ... ... , :東京都=>14047594} {:東京都=>14047594, :神奈川県=>9237337, ... ... ... , :鳥取県=>553407}
- 都道府県別人口のデータをハッシュにした
d
を用いてStatオブジェクトs
を生成 - 人口7344765の県を検索すると、埼玉県であった
- 人口0の県を検索すると、そのような県はないので、空のStatオブジェクトが返される
- 人口最大は東京都
- 人口最小は鳥取県
- 人口の平均値は2683959.6人
- 人口の少ない順に並べると鳥取県・・・・東京都
- それを逆順にし、人口の多い順に並べると東京都・・・鳥取県
クラスを作ったことにより、これらの統計処理がメソッドひとつで実行できるようになりました。 このクラスは都道府県人口以外の統計処理にも使えます。
また、標準偏差やヒストグラム(グラフィックの手段が必要)などもメソッドで用意すればもっと実用的になるでしょう。
今回はサブクラスを取り上げました。 StatクラスはHashのすべてのメソッドを使うことができるので、一から作るよりも少ない労力で作成することができました。 このようにサブクラスの仕組み(これを継承ともいう)はプログラムの資産を活かすことにつながるわけです。