Hash is a data structure stored by related keys. This is in contrast to arrays that store items in an ordered index. The entries in a hash are often referred to as key-value pairs. This creates a linked representation of the data. Most commonly, a hash is generated using symbols as keys and any data type as value. All key-value pairs in a hash are surrounded by curly braces {} and separated by commas. Hash can be created using two syntaxes. The older syntax comes with an => sign to separate keys and values.
1 2 3 | irb :001 > old_syntax_hash = {:name => 'bob'} => {:name=>'bob'} |
The newer syntax was introduced in Ruby 1.9 and is much simpler. As you can see, the results are the same.
1 2 3 | irb :002 > new_hash = {name: 'bob'} => {:name=>'bob'} |
You can also have hashes with multiple key-value pairs.
1 2 3 | irb :003 > person = { height: '6 ft', weight: '160 lbs' } => {:height=>'6 ft', :weight=>'160 lbs'} |
Let’s say you want to add an existing hash.
1 2 3 4 5 6 7 8 9 10 | irb :004 > person[:hair] = 'brown' => "brown" irb :005 > person => {:height=>'6 ft', :weight=>'160 lbs', :hair=>'brown'} irb :006> person[:age] = 62 => 62 irb :007> person => {:height=>'6 ft', :weight=>'160 lbs', :hair=>'brown', :age=>62} |
And what if you want to remove something from an existing hash?
1 2 3 4 5 | irb :008 > person.delete(:age) => 62 irb :009 > person => {:height=>'6 ft', :weight=>'160 lbs', :hair=>'brown'} |
Now how do you get some information from a hash?
1 2 3 | irb :010 > person[:weight] => "160 lbs" |
What if you want to merge two hashes together?
irb: 011> person.merge! (new_hash) => {: height => ‘6 ft’,: weight => ‘160 lbs’,: hair => ‘brown’,: name => ‘bob’} Note that we used the bang (!) suffix to make this change destructive. We could choose to use the merge method instead, which will return a new merged hash, but leave the original person’s hash unchanged.
Repeats the elements in the Hash
Because hashes can have many elements in them, there will come a time when you want to iterate over a hash to do something with each. Repeating hashes is similar to repeating arrays with some slight differences.
1 2 3 4 5 6 7 8 | # iterating_over_hashes.rb person = {name: 'bob', height: '6 ft', weight: '160 lbs', hair: 'brown'} person.each do |key, value| puts "Bob's #{key} is #{value}" End |
We use each method as before, but this time we assign a variable to both the key and the value. In this example, we are setting the key for the key variable and the value to the value variable. Run this program at the command line with ruby iterating_over_hashes.rb to see the result. The output is:
1 2 3 4 5 | Bob's name is bob Bob's height is 6 ft Bob's weight is 160 lbs Bob's hair is brown |
Hash as an optional parameter
You can also use hashes to accept optional parameters when creating methods. This can be useful when you want to give your methods more flexibility and understandability. More options, if you want! Let’s create a method to do that.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # optional_parameters.rb def greeting(name, options = {}) if options.empty? puts "Hi, my name is #{name}" else puts "Hi, my name is #{name} and I'm #{options[:age]}" + " years old and I live in #{options[:city]}." end end greeting("Bob") greeting("Bob", {age: 62, city: "New York City"}) |
Did we use Ruby hash’s empty? to detect if the optional parameter, which is a hash, has anything passed to it. You have not seen this method yet but you can figure out how it works. You can also see the Ruby documentation for the method lookup. Finally, we called the method twice. One use has no optional parameters and the second uses the hash to pass optional parameters. You can see how using this feature can make your methods much more flexible.
And finally, you can also pass the arguments to the greeting method like this: greeting (“Bob”, age: 62, city: “New York City”)
Note that the curly braces, {}, are optional when the hash is the last argument.
Common hashing methods
Let’s look at some of the popular methods that come with the Ruby’s Hash class.
has_key?
Has_key? allows you to check if a hash contains a particular key. It returns a boolean value.
1 2 3 4 5 6 7 | irb :001 > name_and_age = { "Bob" => 42, "Steve" => 31, "Joe" => 19} => {"Bob"=>42, "Steve"=>31, "Joe"=>19} irb :002 > name_and_age.has_key?("Steve") => true irb :003 > name_and_age.has_key?("Larry") => false |
select
The select method allows you to pass a block and will return any key-value pairs that evaluate to true when running through the block.
1 2 3 4 5 | irb :004 > name_and_age.select { |k,v| k == "Bob" } => {"Bob"=>42} irb :005 > name_and_age.select { |k,v| (k == "Bob") || (v == 19) } => {"Bob"=>42, "Joe"=>19} |
fetch
The method allows you to pass a certain key and it will return the value for that key if it exists. You can also specify a return option if that key is not present. Take a look at the Ruby documentation here to see what else could happen.
1 2 3 4 5 6 7 8 9 10 | irb :006 > name_and_age.fetch("Steve") => 31 irb :007 > name_and_age.fetch("Larry") => KeyError: key not found: "Larry" from (irb):32:in `fetch' from (irb):32 from /usr/local/rvm/rubies/ruby-2.5.3/bin/irb:16:in `<main>' irb :008 > name_and_age.fetch("Larry", "Larry isn't in this hash") => "Larry isn't in this hash" |
car
The to_a method returns an array version of your hash when called.
1 2 3 4 5 | irb :009 > name_and_age.to_a => [["Bob", 42], ["Steve", 31], ["Joe", 19]] irb :010 > name_and_age => {"Bob"=>42, "Steve"=>31, "Joe"=>19} |
keys and values
If you want to retrieve all keys or all values from a hash
1 2 3 4 5 | irb :0011 > name_and_age.keys => ["Bob", "Steve", "Joe"] irb :0012 > name_and_age.values => [42, 31, 19] |
Note that the returned values are in an array format. Because it returns an array, you can print out all the keys in a hash function: name_and_age.keys.each {| k | set k}