【編集履歴】
2022/06/09: コメントにて頂いたご指摘内容を元に修正
何に困った?
ハッシュを要素にもつ配列と複数のインスタンス変数をもつインスタンスの扱い方がごっちゃになってしまったので、今一度整理します。
整理ポイントは
- データ群のイメージ
- 値の出力方法
- each文で各要素データの出力
記事最後に、僕が勘違いしたポイントも紹介します。
ハッシュを要素にもつ配列
本記事ではハッシュを要素にもつ配列として以下のものを準備しました。
hoges = [
{fuga1: "aa", fuga2: "ab", fuga3: 0},
{fuga1: "ba", fuga2: "bb", fuga3: 1},
{fuga1: "ca", fuga2: "cb", fuga3: 2}
]
イメージを図で理解
ハッシュを要素にもつ配列は表で考えると分かりやすいです。
データ出力の方法
配列全体の出力
puts hoges
#=> {:fuga1=>"aa", :fuga2=>"ab", :fuga3=>0}
#=> {:fuga1=>"ba", :fuga2=>"bb", :fuga3=>1}
#=> {:fuga1=>"ca", :fuga2=>"cb", :fuga3=>2}
このように、配列の中身(要素)を出力することができます。
hoges配列は{}で囲まれたハッシュを要素に持っており、ハッシュの中身も確認することができます。
また、別の方法としてKernel.#pを用いる方法もあります。
p hoges
#=> [{:fuga1=>"aa", :fuga2=>"ab", :fuga3=>0}, {:fuga1=>"ba", :fuga2=>"bb", :fuga3=>1}, {:fuga1=>"ca", :fuga2=>"cb", :fuga3=>2}]
pは、各要素に対してObject#inspectを行うメソッドで、これによりハッシュの中身も出力されます。
ハッシュの出力
hoge1 = hoges[0] #hogesの0番目の要素をhoge1という変数に代入
puts hoge1
#=> {:fuga1=>"aa", :fuga2=>"ab", :fuga3=>0}
hogesは要素がハッシュの形をしているだけの配列ですので、ハッシュ(要素)を出力する方法は配列から要素を出力する方法と同じです。
ハッシュ内の値を出力
puts hoges[0][:fuga1] #=> aa
配列からハッシュ内の値を取り出すには、2段階の処理をしていると考えると分かりやすいです。
hoges[0]の箇所ではhoges配列の0番目の要素(ハッシュ)を意味し、
[:fuga1]では、そのハッシュのfuga1キーを意味しています。
each文で各要素データの出力
配列から各要素データの出力をするとは、各ハッシュから指定したキーに対応する値を出力するということです。
表で示した配列の縦列を抜き出すイメージです。
hoges.each do |hoge|
puts hoge[:fuga2]
end
#=> ab
#=> bb
#=> cb
each文に関してはProgeteでも十分に説明があるので不要だとは思いますが、一点分かりにくいことがありました。
hogeがhogesの要素の1つであることはeach文の中で初めて定義される
最初にeach構文を見たとき、「このhogeはどこから来たの?」と戸惑いました。
結論は上記の通りで、ぶっちゃけ |〇〇| の中身はなんでもOKです。
ただ、hoges配列という複数形の中身として単数形のhogeを使用することが誰にとっても分かりやすいことは一目瞭然でしょう。
クラスとは
本記事ではクラスとして以下のものを準備しました。
class Hoge
# インスタンス変数の値をやり取りするメソッドを定義
attr_accessor :fuga1
attr_accessor :fuga2
attr_accessor :fuga3
# インスタンス変数の定義
def initialize(fuga1:, fuga2:, fuga3:)
self.fuga1 = fuga1
self.fuga2 = fuga2
self.fuga3 = fuga3
end
end #ここまでがクラスの定義
#インスタンスの生成
hoge1 = Hoge.new(fuga1: "aa", fuga2: "ab", fuga3: 0)
hoge2 = Hoge.new(fuga1: "ba", fuga2: "bb", fuga3: 1)
hoge3 = Hoge.new(fuga1: "ca", fuga2: "cb", fuga3: 2)
イメージを図で理解
クラスを図で表すと下のようになります。
ここでポイントを2点
各インスタンス同士は独立しており、互いに関係していない
説明の便宜上hoge1,hoge2,,としていますが、本来は各インスタンスに順序等は無く、「〇番目のインスタンス」というイメージは持たない方が良さそうです。
インスタンス内の要素はインスタンス変数とそれに対応する値である
ハッシュ内の要素はキーと値のペアでしたが、インスタンス内の要素はインスタンス変数とそれに対応する値となります。
より詳細に説明すると、attr_accessorではインスタンス変数の値をやりとりするメソッドを定義し、
initializeメソッドでそのメソッドをインスタンス変数に代入しています。
よって、Hoge.new(fuga1:~~)に示されるfuga1, fuga2, fuga3 はインスタンス変数となります。
ちなみに、attr_accessorについてはこちらの記事が参考になりました。
https://qiita.com/soicchi/items/5d64bc49d0e16c7602a3
私のような、略語の元ネタが分からないともやもやする方は「(文字列) 読み方」などでググってみましょう。
データ出力の方法
クラス全体の出力
同じクラスを持つ各インスタンスはそれぞれ独立しているので、「クラス全体」という概念がありません。
試しに puts Hoge と命令しても Hoge としか返ってきません。
puts Hoge
#=> Hoge
インスタンスの出力
puts hoge1
#=> #<Hoge:0x000001f1f8db4048>
hoge1インスタンスの中身全体を出力しようと上記のようなコードを書いてみたところ、Hogeクラスであることしか知ることができませんでした。
どうやらputsはstring(文字列)を出力するメソッドであるため、hoge1をto_sメソッドにより無理やり文字列化して出力した結果を返しているようです。
(このあたりは要勉強ですが、今のところは「なるもんはなる。」という意気込みでもよさそう。)
この問題を解決するために、コメントにて教えて頂いたObject#inspectを試してみました。
これにより、そのインスタンスがどんなインスタンス変数とその中身を持っているのかが分かります。
puts hoge1.inspect
#=> #<Hoge:0x0000017f61a92b30 @fuga1="aa", @fuga2="ab", @fuga3=0>
さらにハッシュでも紹介したpメソッドを用いて
p hoge1
#=> #<Hoge:0x0000020f311befa8 @fuga1="aa", @fuga2="ab", @fuga3=0>
でもインスタンスの中身を出力することができます。
また、別の方法としてクラス定義式内でto_sメソッドを定義し、hoge1に対して実行してみました。
def to_s
puts "@fuga= #{fuga1}, @fuga2= #{fuga2}, @fuga3= #{fuga3}"
end
puts hoge1.to_s
#=> @fuga= aa, @fuga2= ab, @fuga3= 0
インスタンスの中身が見たいという場合は上記いずれかの方法で行うと良さそうです。
インスタンス内の指定したインスタンス変数に対応する値を出力
puts hoge1.fuga2
#=> ab
インスタンス内の指定したインスタンス変数に対応する値を出力するには、上記のようなコードを書きます。
ここで、fuga2はインスタンス変数ではなく、attr_accessorにて定義されたメソッドであると考えた方が理解しやすいです。
attr_accessorはinitializeで定義されたインスタンス変数に対応する値を返すメソッドです。
クラス定義式内で定義したメソッドをインスタンスに対しておこなう
クラス定義式内で下記メソッドを定義します。
def output1
puts self.fuga1
end
def double_fuga3
return self.fuga3 * 2
end
そして、output1メソッドをhoge1インスタンスに、double_fuga3メソッドをhoge3インスタンスにおこないます
hoge1.output1 #=> aa
puts "hoge3のfuga3を2倍すると#{hoge3.double_fuga3}"
#=> hoge3のfuga3を2倍すると4
結果として、値を出力することができました。
(おまけ)インスタンスが持つインスタンス変数を出力
puts hoge1.instance_variables
#=> @fuga1
#=> @fuga2
#=> @fuga3
そのインスタンスがどんなインスタンス変数を持っているのかを確認したいときはinstance_variables というのはメソッドを用います。
こちらの記事を参考にしました。
https://qiita.com/rsooo/items/ce4f3c0028312d513e3b
each文で各要素データの出力
複数のインスタンスから、あるインスタンス変数に対応する値を連続して出力したいときは下記のようにコードを書きます。
hoges = [hoge1, hoge2, hoge3]
hoges.each |hoge|
puts hoge.fuga2
end
#=> ab
#=> bb
#=> cb
最初に、インスタンスを要素にもつ配列を作ります。
クラスの説明で「各インスタンスは独立関係」と説明しましたが、hoges配列を作ることによってインスタンスに順番が生まれます。
そしてその中で インスタンス.インスタンス変数を引き渡すメソッド を出力するように命令すると、各インスタンスの指定したインスタンス変数(今回はfuga2)に対応する値を出力します。
僕が間違えた箇所
複数のインスタンスから、あるインスタンス変数に対応するデータを連続して出力するとき、各インスタンスを配列にするところまでは理解していました。
しかし、each文のなかで
hoges = [hoge1, hoge2, hoge3]
hoges.each |hoge|
puts hoge[:fuga2] #fuga2をインスタンス変数に対応する値を取り出すメソッドではなくキーとして扱ってしまった
end
#=> undefined method `[]' for #<Hoge:0x00000239dab66848 @fuga1="aa", @fuga2="ab", @fuga3=0> (NoMethodError)
としてしまいました。
クラスの説明でも書いたとおり、インスタンスはキー要素を持たないので、こちらのコードではエラーが発生してしまいます。
上級者にとっては当たり前だとは思いますが、初学者の私はハッシュとインスタンスがごっちゃになってしまったので、本記事の投稿を境に理解したということにします。