大ちゃんの駆け出し技術ブログ

RUNTEQ受講生のかわいいといわれるアウトプットブログ

【無職に転生 ~ 就職するまで毎日ブログ出す_11】【Ruby】ハッシュの使い方

はじめに

こんにちは、大ちゃんの駆け出し技術ブログです。

タイトルにあるとおり【無職に転生 ~ 就職するまで毎日ブログ出す】というチャレンジをしています!!!!昨日までは就活するまで本気出すでしたが、これだとまるで就活後は頑張らないのかと思われてしまいそうで、、、大人気アニメのタイトルのまるパクリチャレンジです。

https://i.gyazo.com/3a02b7aae4e5e538130d4bb199b55dc7.png

RailsやらRubyやらSQLやらその他Webの知識やらが色々と抜け落ちているのを感じており、知識の定着のためにもアウトプットする機会を増やすためです。加えて、退職して文字通り無職に転生しましてプロニートになり、毎日時間に余裕ができたので引き締めるためにも毎日投稿を思い至りました!

【投稿内容】

  • SQLの難しい処理 (副問合せ、JOINとか複雑な処理が書けない)
  • Rails全般 (純粋に必要な知識が多すぎる、網羅的な理解が足りない)
  • Rubyのあまり使わないメソッドや記述方法 (あまり重要ではないけど特に)
  • Web知識全般 (クッキーやら、セッションやらなんとなくで理解しているものの自分の言葉で説明できない)
  • 書籍 (スタートアップ企業に勤めるので、自分が会社に与える影響やパフォーマンスを高めるためビジネス書を読んでいきます。)

本日やること

本日はハッシュについて復習していきます。普段Railsを学習している時に何かと出てくるハッシュですが、特に自分から進んで使おうとしなかったので、いざ自分から使おうとなるとあまり記述が浮かんでこなかったので、この記事でしっかり復習できればと思います。

いつも大変お世話になっているチェリー本の第5章にかなり具体的に書かれているので、それを参考にしつつ他の記事も引用して開発できたらなと思います。

プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design plusシリーズ)


ハッシュとは

そもそもハッシュとは一言でなんぞやと思いましたので、伊藤さんの言葉を使って説明します。

ハッシュはキーと値の組み合わせでデータを管理するオブジェクトのことです。他の言語では連想配列やディクショナリ(辞書)、マップと呼ばれたりしています。

例えば、以下のような記述はハッシュです。

{"ruby" => "rails","python" => "Django", "JS" => "Vue.js" }

rubyはキーで値はrailsとあるように言語とそのフレームワークをデータとして管理しています。

ちなみにRailsでよく使うケースはparamsから値を取得する時です。

@user = User.find(params[:id])

要素の追加、変更、取得、削除

ハッシュは配列と同じようにデータを管理するオブジェクトなので、追加・変更・取得・削除などが可能です。

追加

まず取得ですが、例えば上述したハッシュにPHPとそのフレームワークのLaravelをハッシュに加えるとします。ハッシュを句追加する構文は【ハッシュ[キー] = バリュー】となります。

hash = {"ruby" => "rails","python" => "Django", "JS" => "Vue.js" }
hash['php'] = "Laravel" # ハッシュ[キー] = バリュー
hash
# => {"ruby"=>"rails", "python"=>"Django", "JS"=>"Vue.js", "php"=>"Laravel"}

1番最後の要素にphpとLaravelのデータが格納されました。

変更

次に変更ですがこれは追加の時と記述は同じです。ここでいう変更はバリューの変更になるので、既に格納されているキーを指定して、その対応するバリューの値が既存のバリューとは別の値を格納することで変更できます。例えば、JSのフレームワークをReactに変更する場合は下記のような記述になります。

hash = {"ruby" => "rails","python" => "Django", "JS" => "Vue.js" }
hash['JS'] = "React"
hash
# => {"ruby"=>"rails", "python"=>"Django", "JS"=>"React"}

取得

要素の取得、つまりバリューの取得は対応するキーをハッシュにつけることで取り出すことができます。構文としては【ハッシュ[キー]】でOKです。

hash["JS"]
# => "React"

ちなみに指定したキーが見つからない場合はnilが返却されます。

hash["Java"]
# => nil

削除

削除の場合はdeleteメソッドを使用します。このメソッドはHashクラスのメソッドで、引数にキーを指定することで、そのキーと対となるバリューの組み合わせをオブジェクトの要素から削除します。

hash = {"ruby" => "rails","python" => "Django", "JS" => "Vue.js" }
hash.delete("JS")
hash
# => {"ruby"=>"rails", "python"=>"Django"}

Hash#delete (Ruby 3.0.0 リファレンスマニュアル)

シンボル

ここでシンボルについても復習。普段Railsで何気なく使っているためあまり理解せずに使っていました。💦ちなみにRailsではどこで使われているのかというと、コールバックとかで使われていますね。:set_userがシンボルです。

before_action :set_user

private

def set_user
    @user = User.find(params[:id])
end

シンボルを表すクラス。シンボルは任意の文字列と一対一に対応するオブジェクトです。 文字列の代わりに用いることもできますが、必ずしも文字列と同じ振る舞いをするわけではありません。同じ内容のシンボルはかならず同一のオブジェクトです。

class Symbol

シンボルは先頭にコロンをつけて任意の文字をセットします。

:apple
:japan
:ruby_is_fun

シンボルと文字列の違い

シンボルと文字列は以下の4点で異なるようです。

① オブジェクトのクラスが違う

classメソッドで出力するとSymbolクラスが確認できます。

:ruby.class
=> Symbol
"ruby".class
=> String

② 整数として管理される

チェリー本では以下のように書かれています。

シンボルはRubyの内部で整数として管理されます。表面的には文字列と同じように見えますが、その中身は整数なのです。

整数として管理されるため処理が文字列よりも高速になることがあります。

:ruby == :ruby # シンボルの方が早い
"ruby" == "ruby"

③ 同じシンボルであれば同じオブジェクト

シンボルは形が同じであればそれは全て同一のオブジェクトとして扱われます。object_idメソッドを使えばわかるのですが、同一名のシンボルは全て同じオブジェクトであることがわかりますが、文字列の場合は全て異なります。

:ruby.object_id
=> 707228
:ruby.object_id
=> 707228
:ruby.object_id
=> 707228
"ruby".object_id
=> 280
"ruby".object_id
=> 300
"ruby".object_id
=> 320
"ruby".object_id
=> 340
"ruby".object_id
=> 360

Object#object_id (Ruby 3.0.0 リファレンスマニュアル)

よって同一名の文字列が大量に使われればそれだけメモリが奪われますが、シンボルの場合同じ名前を使ってもメモリは奪われません。

④ イミュータブル

シンボルは破壊的メソッドでは変更できないイミュータブルなオブジェクトです。

"ruby".upcase!
=> "RUBY"
:ruby.upcase!
(irb):36:in `<main>': undefined method `upcase!' for :ruby:Symbol (NoMethodError)

upcase!メソッドは破壊的メソッド でレシーバーの元の値も変更してしまいますが、シンボルは破壊的メソッドが定義されていないようですので、undefined method (定義されていない)エラーが返ってきます。

ちなみにupcaseメソッドはシンボルでも使えます。

:ruby.upcase
=> :RUBY

大文字のシンボルと小文字のシンボルを比較すると別のオブジェクトであることがわかります。

ruby.upcase.object_id
=> 701988
:ruby.object_id
=> 707228

ハッシュにシンボルを使う

シンボルを紹介したことで再びハッシュ。シンボルはハッシュによく使われるらしいです。ハッシュのキーをシンボルにして、文字列で定義するよりも高速に処理をすることが可能になります。

hash = {:ruby => "rails",:python => "Django", :JS => "Vue.js" }
hash[:ruby]
# => "rails"

ここでさらに【シンボル: 値】という形に直すことができます。 =>とは別の書き方ですね。返り値を見ると上述したものと見た目は変わっていないことから、同じハッシュが定義できていることがわかります。

hash = {ruby: "rails",python: "Django", JS: "Vue.js" }
=> {:ruby=>"rails", :python=>"Django", :JS=>"Vue.js"}

さらにバリューの方にもシンボルを使うことができるので【シンボル: :シンボル】という形でキーバリューを設定できます。

{ruby: :rails, python: :Django, JS: :Vuejs} # シンボルはコロンが挟めないのでここではVue.jsとは書けません。
=> {:ruby=>:rails, :python=>:Django, :JS=>:Vuejs} 

ちなみにこのシンボルシンボルの見た目で思い出したのは下記のような記述ですね。

has_many :likes, dependent: :destroy

destroyやdependentは文字列として新しくメモリを消費する必要はないのでこのように書かれているのかなと思いました。

ハッシュのメソッド

ここではハッシュクラスで定義されているメソッドを紹介します。ハッシュはArrayと比べて利用頻度低いしそこまで使わないと考えていましたが、調べてみるとたくさんありました。

https://i.gyazo.com/68568bf926eff0b66d559134fcdb8f15.png

全部は紹介しきれないので使えそうなメソッドをいくつかピックアップしました。

keys

レシーバーのハッシュのキーを配列として返します。

{ruby: :rails, python: :Django, JS: :Vuejs}.keys
=> [:ruby, :python, :JS]

class Hash

values

上記のkeysとは違いバリューの配列を返します。

{ruby: :rails, python: :Django, JS: :Vuejs}.values
=> [:rails, :Django, :Vuejs]

class Hash

has_key?

引数のキーがハッシュの中に含まれているかをどうか確認するメソッドです。真偽値が返ってきます。

{ruby: :rails, python: :Django, JS: :Vuejs}.has_key? :python
=> true
{ruby: :rails, python: :Django, JS: :Vuejs}.has_key? :java
=> false

class Hash

dig

digメソッドは実際によく自分も使いました。これはキーを引数で指定してハッシュを取得するメソッドです。

{ruby: :rails, python: :Django, JS: :Vuejs}.dig(:ruby)
=> :rails

class Hash

上記の挙動を見ると上述したバリューの取得と挙動が同じではないかと思うかもしれません。

{ruby: :rails, python: :Django, JS: :Vuejs}[:ruby]
=> :rails

しかし、digメソッドはハッシュが二次元になればなるほど便利です。二次元配列とあるようにハッシュにも階層を作ることができます。

hash =  {frameworks: {ruby: :rails, python: :Django, JS: :Vuejs}}
=> {:frameworks=>{:ruby=>:rails, :python=>:Django, :JS=>:Vuejs}}
hash[:frameworks]
=> {:ruby=>:rails, :python=>:Django, :JS=>:Vuejs}
hash[:frameworks][:ruby]
=> :rails
hash[:frameworks][:java]
=> nil

ここで深い階層の値を取り出す時にもしframeworksが誤記載をしてしまうとエラーが起きてしまいます。frameworkとsを外してみます。

hash[:framework][:java]
# undefined method `[]' for nil:NilClass (NoMethodError)

これはhash[:framework]nilが返却され、nilをレシーバーに[:java]が実行されているためです。

それをdigでは回避することができます。

hash.dig(:framework, :java)
=> nil

先ほどとは違い名前が違うシンボルを引数に指定しても返却される値はnilになります。取得する時はこちらの方がエラーになることがなく便利です。ただ、 バグの温床にもなるので濫用は避けたいところです。

each/each_key/each__value

繰り返し処理でもハッシュは利用できます。ブロック内ではキーとバリューを同時に使用することができます。

hash =  {frameworks: {ruby: :rails, python: :Django, JS: :Vuejs}}
hash.each {|k,v| puts k, v}
# 出力
ruby
rails
python
Django
JS
Vuejs

ちなみに変数を一つにすることもできます。その場合、変数の中身はキーとバリューの配列になります。

hash.each {|x| p x}
# 出力
[:ruby, :rails]
[:python, :Django]
[:JS, :Vuejs]

each_keyとeach__valueはレシーバーのハッシュのキーだけを繰り返し処理したい場合やバリューだけを使用したい場合に使います。

hash.each_key {|k| p k}
# 出力
:ruby
:python
:JS
hash.each_value {|v| p v}
# 出力
:rails
:Django
:Vuejs

終わりに

次の記事もハッシュを書きます。ではまた。