Hash vs. Dictionary

Aug 5, 2021 - 5 min read
Hash vs. Dictionary
Share

The Ruby Hash is an implementation of the dictionary concept, where the keys (in the key, value pair) are unique numbers.

A dictionary is a general concept that maps unique keys to non-unique values.

But the Hash is a data structure that maps unique keys to values by taking the hash value of the key and mapping it to a bucket where one or more values are stored.

Any object could be a key as long as it's hash-able (i.e., you can call the hash method on it to get back a number), and comparable.

To be useable as a Hash key, objects must implement the methods hash and eql?. Note: this requirement does not apply if the Hash uses compare_by_id since comparison will then rely on the keys’ object id instead of hash and eql?.

Hashes are ordered (in Ruby), meaning that they maintain the order in which you've added the elements.

h = {}
h[0] = "a"
h[5] = "b"
h[2] = "c"
h # => {0=>"a", 5=>"b", 2=>"c"}

Syntax

The generic Hash syntax in Ruby is this: key => value.

But there's a newer way of writing hashes that resembles JSON objects. And that new syntax looks like this: key: value.

So, here's how to create hashes using both syntaxes:

random_hash = { "a" => 1, 2 => "b" }
person = { name: "Joe", age: 50, "home address": "Main Str. 1" }

As you can see, the second syntax looks nicer (and more modern) than the one using the "hash rocket". The second one uses symbols for the keys. And it works because symbols are used as identifiers in Ruby (you can read more about symbols here).

So, as long as you are using symbols for keys, you can use this nice-looking syntax to create hashes.

But for all other data types, you need to use the "hash rocket". Let's look at a few more examples.

# Using objects as keys
some_obj = Object.new
{ some_obj => 123 } # => {#<Object:0x00005674cefa0100>=>123}
{ ["a", :b, 5] => 123 } # => {["a", :b, 5]=>123}
{ true => 1, false => 2, nil => 3 } # => {true=>1, false=>2, nil=>3}

Hash vs. Array

Both Hash and Array are collections. But arrays use numbers as their index, while hashes can use any object.

Because both of them are collections, they are Enumerableobjects so you can use methods from the Enumerable module on both.

Ruby hash examples

It might help to see a few code examples of using Hash in practice.

Hash and keyword arguments

Historically, the idiomatic way to provide options (i.e., a list of optional, named arguments) was to use the last argument as a hash argument, and send in whatever options you might want.

def some_method(name, age, options = {})
  if bday = options[:bday]
    "#{name} is #{age} years old on #{bday}."
  else
    "#{name} is #{age} years old."
  end
end
some_method("Joe", 50, {bday: "2nd of Aug"})
# => "Joe is 50 years old on 2nd of Aug."

some_method("Joe", 50) # => "Joe is 50 years old."

But now, Ruby gives you the ability to use keyword arguments. And you can also use hashes with keyword arguments.

def some_methos(name:, age:) = "#{name} is #{age} years old"
args = { name: "Joe", age: 50 }
some_method(**args) # => "Joe is 50 years old"

The ** you see above is called the splat operator, and it's used to convert the args hash into keyword arguments.

An interesting way of ignoring keyword arguments (while accepting the ones you neeed) is to omit the variable name after the double splat operator.

def some_method(name:, **) = name

json_str = '{"name": "Joe", "age": 25}'
params = JSON.parse(json_str).transform_keys(&:to_sym)

some_method(**params) # => "Joe"

Sorting a hash

As mentioned previously, Hash includes the Enumerable module, so you can sort its elements by using the sort method.

a_hash = { 2 => "a", 1 => "b", 5 => "c" }

a_hash.sort.to_h # => { 1=>"b", 2=>"a", 5=>"c"}

The sort method returns an Array, so we need to convert the result back to a hash.

Iterating over a hash

Also part of the Enumerable module are various methods for iterating over collections like map, select, find, etc. But Hash also implements its own methods for iterating over its elements like each, each_pair, each_key, each_value, etc.

a_hash = { "a" => 1, "b" => 2, "c" => 3 }

a_hash.each_pair { |k, v| a_hash[k] => v + 1 }
# => {"a"=>2, "b"=>3, "c"=4}

a_hash.each_key { |k| a_hash[k] = k.upcase }
# => {"a"=>"A", "b"=>"B", "c"="C"}

Merging hashes

There's often the case where you have two different hashes, and you want to merge them together to create a single hash out of both.

Ruby gives you the merge method that you can use like so. Notice that the keys that are the same in both will override each other.

first_hash = { a: 1, b: 2, c: 3 }
second_hash = { a: "a", d: 4 }

first_hash.merge(second_hash)
# => {:a=>"a", :b=>2, :c=>3, :d=>4}

Using hashes for memoization

You've probably seen this simple memoization practice in a bunch of places. It's pretty common.

def some_method(foo)
  @foo ||= foo
end

What this does is it checks if the instance variable @foo has a value, and if not, it assigns it the value of foo. The second time you call the method, @foo will have a value, so the method will return it without doing the assignment.

The not so obvious part of this example is that foo could also be a very expensive operation, and not having to execute it every time makes a lot of sense.

For example.

def some_method(foo)
  @foo ||= fetch_somethig_based_on(foo)
end

Conclusion

I hope you've learned a thing or two about hashes and dictionaries by reading this article.

12 Project Ideas
Cezar Halmagean
Software development consultant with over a decade of experience in helping growing companies scale large Ruby on Rails applications. Has written about the process of building Ruby on Rails applications in RubyWeekly, SemaphoreCI, and Foundr.