A programming language like Ruby comes with lots of features and capabilities which we often don’t learn in detail and instead end up rewriting things in our application code. This post looks at a few simple things within the Hash
class that you could be using.
I called this Part 1 since I think there are other things that a Hash lets you do which I might add to another post.
Default Values and Procs
If a key is not found in a hash, the default is to return nil
. However, you can change this to:
- Return a default value (always the same object)
- Run a default
Proc
wich in turn calculates and returns the value (and optionally stores something into the hash)
Let’s look at this. Are you used to doing this?
1
2
h = Hash.new
z = h[key] || some_default
If you want to always return a different default object/ value (e.g., -999) when a key is not found, pass it as a parameter to the Hash.new
when you create the hash.
Change your code to this:
1
2
3
4
5
6
7
8
h = Hash.new(-999)
h['a'] = 65
h['b'] = 66
h['c'] = 67
# Now, do this
h['b'] # => 66
h['z'] # => -999
There’s a special note on this – this returns exactly the same object that you passed when you created the Hash. So, if you pass it an array, you’ll always get back the same array and you can see some seemingly strange behaviour depending how you use it. Read this reply and thread for an explanation.
You can also provide a Proc
that will be run when the key is not found. You set it up as in the sample below and the code will be run every time a key is not found during lookup. It’s important to note that your block/ Proc receives the hash and the key so you can decide if you want to just (calculate and) return a default value or if you want to store something into the hash at that key.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
h1 = Hash.new{|hash, key| key * key}
h2 = Hash.new{|hash, key| hash[key]= key * key}
# Let's put some values in
h1[1] = 85
h1[2] = 95
h2[1] = 85
h2[2] = 95
# Let's see how many values there are
h1.size # => 2
h2.size # => 2
# These return the same values
h1[10] # => 100
h2[10] # => 100
# Let's see how many values there are now
h1.size # => 2
h2.size # => 3
In the sample above, assuming all your keys were numeric, this would have the effect of returning the value stored in the hash for a particular key, or calculating the square of that number and returning it as the value for the key. In the case of h1
, this would be retuned but not be stored into the hash for that key. However, for h2
, this value will also be stored into the hash as the value of that key.
You could use this ability to put together a really simple 1-line cache. If the key is not found, it runs complex_calculation
and stores it into the hash so that the next time round, the value is already available.
1
2
3
4
5
# Create it like this...
my_cache = Hash.new{|hash, key| hash[key]= complex_calculation(key)}
# ...and use it like this
my_cache[param]
WARNING: Default values and procs are not run when you use fetch
to look up a key in the Hash. As mentioned in the documentation, the methods []
, values_at
and dig
return the value determined by its default proc (if any) or else its default value (initially `nil`) when the key is not found.
Also, from the documentation: Note that setting the default proc will clear the default value and vice versa.
Default Values/ Procs with #fetch
If you want to return a default value or execute a default Proc when using the fetch
method on your hash, you need to pass it as a parameter to the fetch
method.
1
2
3
4
5
6
7
8
9
10
11
12
h1 = {}
# Let's put some values in
h1[1] = 85
h1[2] = 95
# Let's see how many values there are now
h1.fetch(42, -999) # => -999 since h[42] was not found
h1.fetch(42) {|key| key * key} # => 1764 since h[42] was not found and the block executed
# Let's see how many values there are
h1.size # => 2
WARNING: When you run fetch
and the key is not found, the block only receives the key that was not found – it does not get the hash. This means that you cannot update the hash if the key is not found when using fetch
– so, the super simple cache above cannot be updated this way.
Getting multiple values back together
Do you do this often?
1
2
3
4
5
6
7
8
9
h1 = {}
# Let's put some values in
h1[:phase] = 85
h1[:sync] = 95
# Somewhere far, far away
phase = h1[:phase]
sync = h1[:sync]
You can simplify this by doing this instead:
1
2
3
4
5
6
7
8
h1 = {}
# Let's put some values in
h1[:phase] = 85
h1[:sync] = 95
# Somewhere far, far away
phase, sync = h1.values_at(:phase, :sync)
In this, values_at
returns an array comprising the values for each of the keys that was provided. If you’re using default values/ procs, these are used to produce values_at
.
Additional Reading
Read the docs! The Ruby hash documentation is at: https://ruby-doc.org/core-3.1.1/Hash.html
In all honesty, I wrote this because I forget these things – and often end up rewriting it in application code myself. Hope you found it useful. If there are more things that you find useful to do, just add them in the comments below.