DEV Community

loading...

Ruby Hash Method 101

Amirul Asyraf
Musician & ProgrammΞr 🤘
・8 min read

What the heck is Ruby Hash ??

You can think of Ruby Hash is kind of an Array without the numerical indexes. You access the Hash values with Keys. A Hash is a data structure used to store data in the form of UNIQUE key-value pairs.

A Hash has certain similarities to an Array, but:

✅ An Array index is always an Integer
✅ A Hash key can be (almost) any object

We are gonna dive 🤿 into the Hash world in Ruby. But not too deep, just at the level where most people dive in :p.

note: I encourage you to open irb in your favourite terminal app. Make sure you get your hands 🙌 dirty.

TL;DR


✅ Hash Data Syntax

Hash has three Syntax style as Ruby 3.0.

1) hash rocket =>

hashy = {:foo => 0, :bar => 1, :baz => 2}
hashy # => {:foo=>0, :bar=>1, :baz=>2}
Enter fullscreen mode Exit fullscreen mode

2) JSON-style syntax

hashy = {foo: 0, bar: 1, baz: 2}
hashy # => {:foo=>0, :bar=>1, :baz=>2} 
Enter fullscreen mode Exit fullscreen mode

note: The Hash key become a Symbol

note: You will get an error if you use the key that's not a bareword or a String

# Raises SyntaxError (syntax error, unexpected ':', expecting =>):
hashy = {0: 'zero'}
Enter fullscreen mode Exit fullscreen mode

3) String

hashy = {'foo': 0, 'bar': 1, 'baz': 2}
hashy # => {:foo=>0, :bar=>1, :baz=>2} 
Enter fullscreen mode Exit fullscreen mode

note: same like number 2. But the key is change to the String, instead of symbol.

And you can mix the styles;

hashy = {foo: 0, :bar => 1, 'baz': 2}
hashy # => {:foo=>0, :bar=>1, :baz=>2}
Enter fullscreen mode Exit fullscreen mode

Back to the top

✅ Common use case of using Hash

1) Give names to objects

book = {author: "Daniel J. Levitin", title: "This is Your Brain on Music}"
book # => {:author=>"Daniel J. Levitin", :title=>"This is Your Brain on Music"}
Enter fullscreen mode Exit fullscreen mode

2) Give names to method arguments

def some_method(hash)
  p hash
end
some_method({foo: 0, bar: 1, baz: 2}) # => {:foo=>0, :bar=>1, :baz=>2}
Enter fullscreen mode Exit fullscreen mode

3) initialize an object

class Book
  attr_accessor :author, :title
  def initialize(hash)
    self.author = hash[:author]
    self.title = hash[:title]
  end
end
book1 = Book.new(author: 'Daniel J. Levitin', title: "'This is Your Brain on Music')"
book1 # => #<Book: @author="Daniel J. Levitin", @title="This is Your Brain on Music">
Enter fullscreen mode Exit fullscreen mode

4) Get the frequency from a list of numbers

numbers = [1, 1, 1, 2, 4, 65, 55, 54, 55]
freq_hash = numbers.each_with_object(Hash.new(0)) { |number, hash| hash[number] += 1 }

puts "#{freq_hash}"
# {1=>3, 2=>1, 4=>1, 65=>1, 55=>2, 54=>1}
Enter fullscreen mode Exit fullscreen mode

Another tricks is using tally method.

numbers = [1, 1, 1, 2, 4, 65, 55, 54, 55]
numbers.tally
# output
# {1=>3, 2=>1, 4=>1, 65=>1, 55=>2, 54=>1}
Enter fullscreen mode Exit fullscreen mode

note: This is for Ruby 2.7+ only. :P

5) Specify routes in Ruby on Rails

This is a line from the config/routes.rb file, the Router in a Rails application:

# defines a GET route mapped to the new action in the PostsController

get '/posts/new', to: 'posts#new'
Enter fullscreen mode Exit fullscreen mode

The example of the above could be rewritten as:

get('/posts/new', { to: 'posts#new' })
Enter fullscreen mode Exit fullscreen mode

The Hash is the second parameter passed to the get(...) method.

Back to the top

✅ How to Create a Hash

Here are three ways to create a Hash:

1) Method Hash.new

You can create a Hash by calling method Hash.new

h = Hash.new #Define empty hash
h # => {}
h.class # => Hash

h[:first] = 10
h[:second] = 20
h[:third] = 30

puts h # => {:first=>10, :second=>20, :third=>30}
Enter fullscreen mode Exit fullscreen mode

2) Method Hash[]

You can create a Hash by calling method Hash[].

Create an empty Hash:

h = Hash[]
h # => {}
Enter fullscreen mode Exit fullscreen mode

Create a Hash with initial entries:

h = Hash[foo: 0, bar: 1, baz: 2]
h # => {:foo=>0, :bar=>1, :baz=>2}
Enter fullscreen mode Exit fullscreen mode

3) Literal form {}

You can create a Hash by using its literal form (curly braces).

Create an empty Hash:

h = {}
h # => {}
Enter fullscreen mode Exit fullscreen mode

Create a Hash with initial entries:

h = {foo: 0, bar: 1, baz: 2}
h # => {:foo=>0, :bar=>1, :baz=>2}
Enter fullscreen mode Exit fullscreen mode

Back to the top

✅ How to create or update a Hash value

We can use instance method []=:

h = {foo: 0, bar: 1, baz: 2}
h[:bat] = 3 # => 3
h # => {:foo=>0, :bar=>1, :baz=>2, :bat=>3}

h[:foo] = 4 # => 4
h # => {:foo=>4, :bar=>1, :baz=>2, :bat=>3}
Enter fullscreen mode Exit fullscreen mode

Back to the top

✅ Values in a Ruby Hash

Values can be any Ruby Object. Including:

  • Strings
  • Integers & Floats
  • Arrays

note Keys are unique, you can only have one :foo key, or one :bar key. When you add the same key twice, the latter will override the former value.

Back to the top

✅ Hash Entry Order

A Hash object presents its entries in the order of their creation.

A new Hash has its initial ordering per the given entries:

h = Hash[foo: 0, bar: 1]
h # => {:foo=>0, :bar=>1}
Enter fullscreen mode Exit fullscreen mode

New entries are added at the end:

h[:baz] = 2
h # => {:foo=>0, :bar=>1, :baz=>2}
Enter fullscreen mode Exit fullscreen mode

Updating a value does not affect the order:

h[:baz] = 3
h # => {:foo=>0, :bar=>1, :baz=>3}
Enter fullscreen mode Exit fullscreen mode

But re-creating a deleted entry can affect the order:

h.delete(:foo)
h[:foo] = 5
h # => {:bar=>1, :baz=>3, :foo=>5}
Enter fullscreen mode Exit fullscreen mode

Back to the top

✅ How to delete a Hash entry

The simplest way is using instance method delete:

h = {foo: 0, bar: 1, baz: 2}
h.delete(:bar) # => 1
h # => {:foo=>0, :baz=>2}
Enter fullscreen mode Exit fullscreen mode

Back to the top

✅ How to Access Values From a Hash

Hash value/element are access by particular key.

The simplest way to retrieve a Hash value is using instance method [] :

h = {foo: 0, bar: 1, baz: 2}
h[:foo] # => 0
Enter fullscreen mode Exit fullscreen mode

We also can use fetch method. It does same as the square bracket lookup [], but it will raise an error if the key is not defined:

$ irb
> dictionary = { "one" => "satu" } #Malay language 🇲🇾
> dictionary.fetch("one")
=> "satu"
> dictionary.fetch("two")
KeyError: key not found: "two"
Enter fullscreen mode Exit fullscreen mode

We can prevent this error using the default value.

Back to the top

✅ Extract a nested Hash value

dig is handy for nested Hash.

h = { foo: {bar: {baz: 11}}}

h.dig(:foo, :bar, :baz)   # => 11
h.dig(:foo, :zot, :xyz)   # => nil

g = { foo: [10, 11, 12] }
g.dig(:foo, 1)            # => 11
Enter fullscreen mode Exit fullscreen mode

note: This is only for Ruby 2.3+

Back to the top

✅ Hash Keys 101

Hash Key Equivalence

From Documentation: Two objects are treated as the same hash key when their hash value is identical and the two objects are eql? to each other.

irb> g = {foo: 1}
=> {:foo=>1}
irb> h = {foo: 1}
=> {:foo=>1}
irb> g.eql?(h)
=> true
Enter fullscreen mode Exit fullscreen mode

Modifying an Active Hash Key

Modifying a Hash key while it is in use damages the hash index.

This Hash has keys that are Arrays:

a0 = [ :foo, :bar ]
a1 = [ :baz, :bat ]
h = {a0 => 0, a1 => 1}
h.include?(a0) # => true
h[a0] # => 0
a0.hash # => 110002110
Enter fullscreen mode Exit fullscreen mode

Modifying array element a0[0] changes its hash value:

a0[0] = :bam
a0.hash # => 1069447059
Enter fullscreen mode Exit fullscreen mode

And damages the Hash index:

h.include?(a0) # => false
h[a0] # => nil
Enter fullscreen mode Exit fullscreen mode

You can repair the hash index using method rehash:

h.rehash # => {[:bam, :bar]=>0, [:baz, :bat]=>1}
h.include?(a0) # => true
h[a0] # => 0
Enter fullscreen mode Exit fullscreen mode

A String key is always safe. That's because an unfrozen String passed as a key will be replaced by a duplicated and frozen String:

s = 'foo'
s.frozen? # => false
h = {s => 0}
first_key = h.keys.first
first_key.frozen? # => true
Enter fullscreen mode Exit fullscreen mode

Back to the top

✅ Default values

By default, accessing a key which has not been added to the hash returns nil, meaning it is always safe to attempt to look up a key's value:

my_hash = {}

my_hash[:name] # => nil
Enter fullscreen mode Exit fullscreen mode

Hashes can also contain keys in strings. If you try to access them normally it will just return a nil, instead you access them by their string keys:

my_hash = { "name" => "asyraf" }

my_hash[:name]    # => nil
my_hash["name"]   # => asyraf
Enter fullscreen mode Exit fullscreen mode

You can retrieve the default value with method default:

h = Hash.new
h.default # => nil
Enter fullscreen mode Exit fullscreen mode

You can set the default value by passing an argument to method Hash.new or with method default=

h = Hash.new(100)
h.default # => 100

h.default = 99
h.default # => 99
Enter fullscreen mode Exit fullscreen mode

Back to the top

✅ How to Merge Two Ruby Hashes

I think you can guess the method name :p. Tadaaaa, we will use merge method.

defaults    = { a: 1, b: 2, c: 3 }
preferences = { c: 4 }

defaults.merge!(preferences)
# {:a=>1, :b=>2, :c=>4}
Enter fullscreen mode Exit fullscreen mode

Notice that because keys are unique, newer values overwrite older values.

Back to the top

✅ Multiple Hash values for one key

Malaysia = {
  food: [
    "Nasi Lemak",
    "Roti Canai"
  ],
  city: [
    "Kuala Lumpur",
    "Malacca City",
    "George Town"
  ]
}

Malaysia[:city][1]
Enter fullscreen mode Exit fullscreen mode

Where Malaysia[:city] gives you an array & [1] gives you the 2nd element from that array.

The key is a symbol & the values are arrays. When you access the hash, you get an array back which you access normally, like any other array.

Back to the top

✅ Get All Keys and Values From a Hash

If you want a list of all the keys, good news, there is a method for that!

Here it is:

{ foo: 1, bar: 2 }.keys
# [:foo, :bar]
Enter fullscreen mode Exit fullscreen mode

There’s also a method for values:

{ foo: 1, bar: 2 }.values
# [1, 2]
Enter fullscreen mode Exit fullscreen mode

Back to the top

✅ Check if key exists in hash

If you want to know if a key exists in a hash, use the key? method.

hash_data = {'Country'=>"Malaysia",'Food'=>"Nasi Lemak",}
puts hash_data.key?("Country") #true
puts hash_data.key?("Code") #false
Enter fullscreen mode Exit fullscreen mode

Back to the top

✅ How to change a Hash to Array

We can do Hash 🔁 Array. Converting a hash of key/value pairs into an array will produce an array containing nested arrays for pair:

{ :a => 1, :b => 2 }.to_a # => [[:a, 1], [:b, 2]]
Enter fullscreen mode Exit fullscreen mode

In the opposite direction a Hash can be created from an array of the same format:

[[:x, 3], [:y, 4]].to_h # => { :x => 3, :y => 4 }
Enter fullscreen mode Exit fullscreen mode

Similarly, Hashes can be initialized using Hash[] and a list of alternating keys and values:

Hash[:a, 1, :b, 2] # => { :a => 1, :b => 2 }
Enter fullscreen mode Exit fullscreen mode

Or from an array of arrays with two values each:

Hash[ [[:x, 3], [:y, 4]] ] # => { :x => 3, :y => 4 }
Enter fullscreen mode Exit fullscreen mode

Hashes can be converted back to an Array of alternating keys and values using flatten():

{ :a => 1, :b => 2 }.flatten # => [:a, 1, :b, 2]
Enter fullscreen mode Exit fullscreen mode

Back to the top


Super cool

Pretty cool, hah... 😎

There are other cool stuff on Ruby Hash. But as i said, this is just a level of most of people dive in.

Maybe for specific tricks like Hash iteration, sorting and other methods for future articles :P.

And probably this is my notes about Ruby Hash, I will add new stuff as I get new knowledge or experience with it.

Thank you for traveling with me 🚀, I hope you enjoyed the journey 🔥.

The End


Resources:

1, 2, 3, 4, 5, 6, 7

Discussion (0)