How to Use Ruby Hashes to Abstract, Model & Solve Common Logic Problems

Posted by David Watson . on May 3, 2018

3The world of programming wouldn’t be the same if the hash tables or mappings were not introduced. They mean so much to the programmers because they are very essential for the storage of various data that is related to the program during the run and compile time. For those who still don’t understand what a hash table is, it is table containing pairs of key and value associated with the program.

Ruby, one of the most commonly used programming language nowadays, too makes use of this feature. In Ruby, it is simply called hashes. Here we are showcasing some of the coolest things that you can do using these hashes while you write your next program using Ruby.

More than just a table

Most of the programmers simply use the hashes to store the key-value pairs. They don’t look any further. Is there anything more to the hashes than just storing and retrieving operation? The answer is yes! Analysing the hashes will help the programmer give birth to more simple logic and algorithms that will replace the complicated ones.

Let us consider the following case. Write a program that checks whether the entered number is even or odd and whether it is divisible by 5.

There are two conditions that affect the outcome (n % 2 == 0) and (n % 5 == 0)

def sample(n)
if (n % 2 == 0) && (n % 5 != 0)
‘even, but not divisible by 5’
elsif (n % 2 != 0) && (n % 5 == 0)
‘odd and divisible by 5’
elsif (n % 2 == 0) && (n % 5 == 0)
‘even and divisible by 5’
else
‘odd but not divisible by 5’
end
end

to implement this to the first 50 integers,

(1..50).each {|n| puts sample(n)}

Quite simple, wasn’t it? You may be wondering what hashes have to do with the above code. Everything! Let me explain how it works. For each number from 1 to 50, the function sample is called with the current number being passed as the parameter. For each function call, the function returns some Boolean values and prints the outcome. Here is truth table showing the result.

(n % 2== 0) (n % 5 == 0) Outcome
True false ‘’even, but not divisible by 5’
False true ‘odd and divisible by 5’
True true ‘even and divisible by 5’
False false ‘odd but not divisible by 5’

If you take a close analysis of the above truth table, you can find that, the outcome column consists of some discrete values that can be used as hash keys. What we are going to do now is rewriting the above truth table to match our hash. The result would be something like this

 Key                                                                  Value
‘’even, but not divisible by 5’                      (n % 2 == 0) && (n % 5 != 0)
‘odd and divisible by 5’                                (n % 2 != 0) && (n % 5 == 0)
‘even and divisible by 5’                              (n % 2 == 0) && (n % 5 == 0)
‘odd but not divisible by 5’                          (n % 2 != 0) && (n % 5 != 0)

Changing the algorithm

There you have the hash table. But what more can it does? Are you the one of those talented programmers who smells a simple algorithm from the above hash table? I guess you’re not because if you were one, you wouldn’t be reading this. The code can be re written without using the ‘if else’ statements as follows.

def declarative(n)
h = {
”even, but not divisible by 5′ => (n % 2 == 0) && (n % 5 != 0),
‘ odd and divisible by 5’’ => (n % 2 != 0) && (n % 5 == 0),
‘ even and divisible by 5’’ => (n % 2 == 0) && (n % 5 == 0),
‘odd but not divisible by 5’=> (n % 2!= 0) && (n % 5 != 0)
}
h.key(true)
end

What you are seeing now is an example of self-declarative programming. The program does not calculates the outcome for each number, instead we declare the conditions that determines the keys and makes the data structure do the rest of the work. So how does it function? From the above code, it is obvious that all the conditions are mutually exclusive. The benefit is that only one condition will return ‘true’ value to any given input. All other conditions will return false as the key for that particular input. The idea is to return the key with the true value for any given input. Let us check the examples

puts declarative(2) #=> even but not divisible by 5
puts declarative(10) #=> even and divisible by 5
puts declarative(15) #=> odd and divisible by 5
puts declarative(9) #=> odd but not divisible by 5

This method of coding is more convenient and appealing than using the ‘if else’ ladders. It takes less computational time also.

Memoization and Recursion of hashes

As the name suggests memoization is the technique used to store the retrived data for later use. Why not use hashes for that? The memorization can be easily set up by using the hashes.
Recursion is the term used to specify a method calling itself from its own definition. Let us consider the factorial problem. The conventional way of implementing it is:

def fact(x)
x == 1 ? 1: x * fact(x-1)
end

it can be memorized using the following piece of code

factorial_hash ||= Hash.new do |hash,key|
hash[key] = key * hash[key-1]
end
# initialize special cases
fib_hash[1] = 1; fib_hash[2] = 2

It uses an integer array to store the outcome in the hash table. For an input of n, the code will be run n times, each time storing separate values in the array. Passing the number to look up in the call factorial_has[number] will show up the factorial of that number. By using the hashes for the memorization, the computation time of the processor reduces in a considerable amount and thereby increasing the performance of the system

Conclusion

We have shown you how powerful the hashes can be and their influence in writing better algorithms. So next time you are writing a program in Ruby, make sure you make the maximum out of these hashes.

Comments
  1. Rafique Mustak

Leave a Comment

Your email address will not be published. Required fields are marked *