Ruby Hash Method 101
Tags: ruby, rails, datastructure, beginners
Published: 2021-06-25 01:56:16

## 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.

<a id="top"></a>  

# TL;DR

- [1️⃣ Hash Data Syntax](#hash-data-syntax)
- [2️⃣ Common use case of using Hash](#common-use-case-of-using-hash)
- [3️⃣ How to Create a Hash](#how-to-create-a-hash)
- [4️⃣ How to create or update a Hash value](#how-to-create-or-update-a-hash-value)
- [5️⃣ Values in a Ruby Hash](#values-in-a-ruby-hash)
- [6️⃣ Hash Entry Order](#hash-entry-order)
- [7️⃣ How to delete a Hash entry](#how-to-delete-a-hash-entry)
- [8️⃣ How to Access Values From a Hash](#how-to-access-values-from-a-hash)
- [9️⃣ Extract a nested Hash value](#extract-a-nested-hash-value)
- [🔟 Hash Keys 101](#hash-keys-101)
- [1️⃣1️⃣ Default Values](#default-values)
- [1️⃣2️⃣ How to Merge Two Ruby Hashes](#how-to-merge-two-ruby-hashes)
- [1️⃣3️⃣ Multiple Hash values for one key](#multiple-hash-values-for-one-key)
- [1️⃣4️⃣ Get All Keys and Values From a Hash](#get-all-keys-and-values-from-a-hash)
- [1️⃣5️⃣ Check if key exists in hash](#check-if-key-exists-in-hash)
- [1️⃣6️⃣ How to change a Hash to Array](#how-to-change-a-hash-to-array)

---

## ✅ Hash Data Syntax

Hash has three Syntax style as *Ruby 3.0*.

1) `hash rocket =>`

```ruby
hashy = {:foo => 0, :bar => 1, :baz => 2}
hashy # => {:foo=>0, :bar=>1, :baz=>2}
```

2) `JSON-style syntax`

```ruby
hashy = {foo: 0, bar: 1, baz: 2}
hashy # => {:foo=>0, :bar=>1, :baz=>2} 
```
> **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'}
```

3) `String`

```ruby
hashy = {'foo': 0, 'bar': 1, 'baz': 2}
hashy # => {:foo=>0, :bar=>1, :baz=>2} 
```
> **note:** same like number 2. But the key is change to the `String`, instead of symbol.

And you can **mix the styles**;

```ruby
hashy = {foo: 0, :bar => 1, 'baz': 2}
hashy # => {:foo=>0, :bar=>1, :baz=>2}
```

[Back to the top](#top)


## ✅ Common use case of using Hash

1) Give names to objects

```ruby
book = {author: "Daniel J. Levitin", title: This is Your Brain on Music}
book # => {:author=>"Daniel J. Levitin", :title=>"This is Your Brain on Music"}
```

2) Give names to method arguments

```ruby
def some_method(hash)
  p hash
end
some_method({foo: 0, bar: 1, baz: 2}) # => {:foo=>0, :bar=>1, :baz=>2}
```

3) initialize an object

```ruby
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">
```

4) Get the frequency from a list of numbers

```ruby
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}
```
Another tricks is using `tally` method.

```ruby
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}
```
> **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:
```ruby
# defines a GET route mapped to the new action in the PostsController

get '/posts/new', to: 'posts#new'
```

The example of the above could be rewritten as:

```ruby
get('/posts/new', { to: 'posts#new' })
```

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

[Back to the top](#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`

```ruby
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}
```

2) Method `Hash[]`

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

Create an empty Hash:

```ruby
h = Hash[]
h # => {}
```

Create a Hash with initial entries:

```ruby
h = Hash[foo: 0, bar: 1, baz: 2]
h # => {:foo=>0, :bar=>1, :baz=>2}
```

3) Literal form `{}`

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

Create an empty Hash:
```ruby
h = {}
h # => {}
```

Create a Hash with initial entries:
```ruby
h = {foo: 0, bar: 1, baz: 2}
h # => {:foo=>0, :bar=>1, :baz=>2}
```

[Back to the top](#top)

## ✅ How to create or update a Hash value

We can use instance method `[]=`:

```ruby
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}
```

[Back to the top](#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](#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:
```ruby
h = Hash[foo: 0, bar: 1]
h # => {:foo=>0, :bar=>1}
```

New entries are added at the end:
```ruby
h[:baz] = 2
h # => {:foo=>0, :bar=>1, :baz=>2}
```

Updating a value does not affect the order:
```ruby
h[:baz] = 3
h # => {:foo=>0, :bar=>1, :baz=>3}
```

But re-creating a deleted entry can affect the order:
```ruby
h.delete(:foo)
h[:foo] = 5
h # => {:bar=>1, :baz=>3, :foo=>5}
```

[Back to the top](#top)

## ✅ How to delete a Hash entry

The simplest way is using instance method `delete`:

```ruby
h = {foo: 0, bar: 1, baz: 2}
h.delete(:bar) # => 1
h # => {:foo=>0, :baz=>2}
```

[Back to the top](#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 `[]` :

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

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**:

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

We can prevent this error using the [default value](#default-values).

[Back to the top](#top)

## ✅ Extract a nested Hash value

`dig` is handy for nested `Hash`.

```ruby
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
```
> **note:** This is only for **Ruby 2.3+**

[Back to the top](#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.

```ruby
irb> g = {foo: 1}
=> {:foo=>1}
irb> h = {foo: 1}
=> {:foo=>1}
irb> g.eql?(h)
=> true
```

### 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:
```ruby
a0 = [ :foo, :bar ]
a1 = [ :baz, :bat ]
h = {a0 => 0, a1 => 1}
h.include?(a0) # => true
h[a0] # => 0
a0.hash # => 110002110
```
Modifying array element `a0[0]` changes its hash value:
```ruby
a0[0] = :bam
a0.hash # => 1069447059
```
And damages the Hash index:
```ruby
h.include?(a0) # => false
h[a0] # => nil
```
You can repair the hash index using method `rehash`:
```ruby
h.rehash # => {[:bam, :bar]=>0, [:baz, :bat]=>1}
h.include?(a0) # => true
h[a0] # => 0
```
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:
```ruby
s = 'foo'
s.frozen? # => false
h = {s => 0}
first_key = h.keys.first
first_key.frozen? # => true
```

[Back to the top](#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*:

```ruby
my_hash = {}

my_hash[:name] # => nil
```

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**:
```ruby
my_hash = { "name" => "asyraf" }

my_hash[:name]    # => nil
my_hash["name"]   # => asyraf
```

You can retrieve the default value with method `default`:
```ruby
h = Hash.new
h.default # => nil
```
You can set the default value by passing an argument to method `Hash.new` or with method `default=`
```ruby
h = Hash.new(100)
h.default # => 100

h.default = 99
h.default # => 99
```

[Back to the top](#top)

## ✅ How to Merge Two Ruby Hashes

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

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

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

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

[Back to the top](#top)

## ✅ Multiple Hash values for one key

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

Malaysia[:city][1]
```

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](#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:
```ruby
{ foo: 1, bar: 2 }.keys
# [:foo, :bar]
```
There’s also a method for values:
```ruby
{ foo: 1, bar: 2 }.values
# [1, 2]
```

[Back to the top](#top)

## ✅ Check if key exists in hash

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

```ruby
hash_data = {'Country'=>"Malaysia",'Food'=>"Nasi Lemak",}
puts hash_data.key?("Country") #true
puts hash_data.key?("Code") #false
```

[Back to the top](#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**:
```ruby
{ :a => 1, :b => 2 }.to_a # => [[:a, 1], [:b, 2]]
```
In the opposite direction a `Hash` can be created from an `array` of the same format:
```ruby
[[:x, 3], [:y, 4]].to_h # => { :x => 3, :y => 4 }
```
Similarly, Hashes can be initialized using `Hash[]` and a list of alternating keys and values:
```ruby
Hash[:a, 1, :b, 2] # => { :a => 1, :b => 2 }
```
Or from an array of arrays with two values each:
```ruby
Hash[ [[:x, 3], [:y, 4]] ] # => { :x => 3, :y => 4 }
```
Hashes can be converted back to an `Array` of alternating keys and values using `flatten()`:
```ruby
{ :a => 1, :b => 2 }.flatten # => [:a, 1, :b, 2]
```
[Back to the top](#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](https://www.rubyguides.com/2020/05/ruby-hash-methods/), [2](https://rubyapi.org/3.0/o/hash), [3](https://kalkicode.com/ruby/ruby-hash), [4](http://ruby-for-beginners.rubymonstas.org/built_in_classes/hashes.html), [5](https://riptutorial.com/ruby/topic/288/hashes), [6](https://outline.com/jTTW9U), [7](https://medium.com/epfl-extension-school/introduction-to-hashes-in-ruby-and-in-rails-be8ac5d4f58a)