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 Enumerable
objects 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.