ミズノブログ

ミズノです。プログラミング・子育て・経年変化するもの など好きなことを雑多に書きます。

【Ruby入門】構造化データにStructクラスを利用する

Ruby門中です。今日はStructクラスについて調べました。

Structクラスとは

一言で言うと、クラスより簡単に構造体を作成する仕組みです。
docs.ruby-lang.org

Structクラスの特徴

定義が簡単

Struct.newで引数に渡したシンボルに対応するメンバとアクセサメソッドを持つクラスを生成します。

  # 気温情報を持つ構造化データ
  Temperature = Struct.new(:date, :high, :low)
  temp = Temperature.new('2018/01/01', 8.0, 0.5)
  temp.high # => 8.0

ブロックでメソッドも定義可能

ブロックを利用することでメソッドを定義することも可能です。

  Temperature = Struct.new(:date, :high, :low) do
    def mean
      (high + low) / 2.0
    end
  end

Structクラスの使いどころ

構造化データを用意する際、Hash、Class、Structあたりが選択肢となります。
以下のような時にStructクラスを利用すると良さそうです。

  • ちょっとした構造化データが必要だが、クラス作るほどではない
    • 利用箇所が限定的
    • その構造化データが今後拡張される想定がない(継承、ミックスインなどしない)
  • Hashのように、メンバが動的に変更されたくない
    • 動的に変更する構造化データが欲しい場合、OpenStructクラスも用意されている

HashとStructの比較サンプルソース

以下、Hashを利用した場合とStructを利用した場合の比較サンプルソースです。

require 'csv'

# --- Hashを利用した年間気温クラス
class AnnualWeatherWithHash
  def initialize(file_name)
    @readings = []
    CSV.foreach(file_name, headers: true) do |row|
      @readings << {
        date: Date.parse(row[2]),
        high: row[10].to_f,
        low: row[11].to_f,
      }
    end
  end

  # 平均気温
  def mean
    return 0.0 if @readings.size.zero?
    total = @readings.reduce(0.0) do |sum, reading|
      sum + (reading[:high] + reading[:low]) / 2.0
    end
    total / @readings.size.to_f
  end
end

# --- Structを利用した年間気温クラス
class AnnualWeatherWithStruct
   # Structを作成しReading定数に保持
  Reading = Struct.new(:date, :high, :low) do
    def mean  # ブロックでメソッド定義することも可能
      (high + low) /2.0
    end
  end

  def initialize(file_name)
    @readings = []
    CSV.foreach(file_name, headers: true) do |row|
      # Reading定数をクラスのように利用することができる
      @readings << Reading.new(Date.parse(row[2]), row[10].to_f, row[11].to_f)
    end
  end

  # 平均気温
  def mean
    return 0.0 if @readings.size.zero?
    total = @readings.reduce(0.0) { |sum, reading| sum + reading.mean }
    total / @readings.size.to_f
  end
end

参考

  • Effective Ruby (項目10: 構造化データにはHashではなくStructを使おう)