項目10 構造化データの表現にはHashではなくStructを使おう
CSVファイルの取り込み時などに、それ用のクラスを作るの面倒だなという場合は、Structが良いというお話。
私もこの手のやつはHashだと扱いにくいので、自然とStructを使うようにしていました。
でも最近、Nestされた構造化データを扱うときにはHashが便利になるケースもあると最近学びました。
話がそれるので、この話はまたの機会に。
以下、本題。
- 作者: Peter J. Jones,arton,長尾高弘
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/09
- メディア: 大型本
- この商品を含むブログ (13件) を見る
Hashの問題点
CSVファイルの読み込み時などにHashを使ってしまうと以下のような問題が起きます。
1. keyを意識しないといけなくなる
要はインターフェースとして定義されていないので、利用する際に一々定義されている箇所を確認しないといけなくなります。
2. Hasnなのでゲッターメソッドでアクセスできない
そのままですが、他にもtypo時にNoMethodErrorが発生しない。
3. 各Hash間を操作するようなメソッドが作れない
例えば、各行に月間の気温統計のCSVがあるとして、そのデータの平均気温も知りたい場合、 各月の平均気温も知らないといけないが、それを定義する場所がない。
require('csv') class AnualWeather def initialize(file_name) @readings = [] # Hashのkeyを意識しないといけない 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| # 各月の平均気温を求めるロジック # 本当はここだけ抽象化したい。 # あとgetter method使えない。 sum + (reading[:high] + reading[:low]) / 2.0 end total / @readings.size.to_f end end
Structを使うと
最初の2つの問題が解消されます。
class AnnualWeather # 項目定義が分離されて見やすい Reading = Struct.new(:date, :high, :low) def initialize @readings = [] CSV.foreach(file_name, headers: true) do |row| @readings << Readings.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) do |sum, reading| # getter methodが使える sum + (reading.high + reading.low) / 2.0 end total / @readings.size.to_f end end
さらにblockを渡すことで
3つ目の問題が解消されます。
Reading = Struct.new(:date, :high, :low) do def initizlize super(date, high, low * 1.1) end # ここに定義できる。わかりやすい。 def mean (high + low) / 2.0 end end
覚えておくべき事項
- 新しいクラスを作るほどでもない構造化データを扱うときには、HashではなくStrutを使うようにしよう
- Struct::newの戻り値を定数に代入し、その定数をクラスのように扱おう
余談
私はどっちかというと上記のblockを渡す方法より、以下のようにclassにしてしまって使うことの方が多かったです。
どっちでもいい気がしますが、以下の場合initializeを書くことができます。
class Reading < Struct.new(:date, :high, :low) def initialize(date, high, low) # do something super(date, high, low * 1.1) end end