Intermediate Ruby - Hash Methods

Question Click to View Answer
languages = {
  "ruby" => "awesome",
  "javascript" => "weird, but cool",
  "java" => "don't know"
}

Return the value that corresponds with the "ruby" key. Describe the Ruby Hash data structure at a high level.

languages["ruby"] # returns "awesome"

A hash is an ordered collection of key / value pairs. The keys in a hash cannot be duplicates and any Ruby object can be a key or a value.

What does the following code return?

browsers = {
  :favorite => :chrome,
  :favorite => :firefox,
  :worst => :internet_explorer
}
browsers[:favorite]
:firefox

Duplicate keys are not allowed in a hash. When Ruby detects a duplicate key, it simply deletes the first key / value pair that is a duplicate.

Is the following hash valid?

weird_hash = {
  [1, 2, 3] => Object.new(),
  Hash.new => :mousie
}

The weird_hash is odd, but valid. Any object can be a key or value in a Ruby hash. Symbols are typically used as hash keys because they are descriptive and efficient, but any object can be used.

Return the physical costs associated with hockey in the following sports hash.

sports = {
  :hockey => {
    :benefits => [:fun, :teamwork],
    :costs => {
      :financial => ["mad money"],
      :physical => [:concussions]
    }
  }
}
sports[:hockey][:costs][:physical]

Nested hashes are a good way to organize complicated data structures. Descriptive key names make code that indexes into complicated hash structures readable.

people = [
  {:name => "bob", :age => 42},
  {:name => "frank", :age => 13}
]

Create an array of all the first names in the people array. The result should be ["bob", "frank"]

people.map {|person| person[:name]}
class_grades = {
  :section_one => [88, 74, 64],
  :section_two => [99, 100]
}

Return an average of all the grades in :section_one and :section_two.

grades = class_grades.values.flatten
grades.inject(0) do |sum, grade|
  sum += grade
end.to_f / grades.length

The Hash#values method returns an array of the values in a hash and the Array#flatten method converts the nested Array structure into a simple array. The inject() method is used to calculate the sum of all grades in the array and compute the average.

books = {
  :hemmingway => "old man and the sea",
  :vonnegut => ["Sirens of Titan"]
}

Show how the books hash can be rewritten with the shortcut hash syntax.

books = {
  hemmingway: "old man and the sea",
  vonnegut: ["Sirens of Titan"]
}

When the keys of a hash are symbols, the colon can be placed after the key and the hash rocket (=>) can be deleted. Symbols are often the keys of hashes and the shortcut syntax is frequently used as it does not require as much typing.

cities = {
  santa_cruz: "chill",
  new_york: "intense"
}

Use the cities hash to print out the following strings:

"santa_cruz is chill"
"new_york is intense"
cities.each do |city, vibe|
  puts "#{city} is #{vibe}"
end

The Hash#each method iterates over the ordered hash and passes each key and value to the block. In this example, the city block variable corresponds with the keys and the vibe block variable corresponds with the values.

movie = {
  "dude" => [],
  "where's" => "",
  "my" => {},
  "car" => Object.new
}

Use the movie hash to construct the string "dude where's my car" (notice that the keys of the movie hash are the components of the desired string).

movie.keys.join(" ")

The Hash#keys method returns an array of all the keys in a hash.

dude = {
  age: 44,
  weight: 250,
  net_worth: 25
}

Return the product of all the values in the dude hash (the result should be 44 * 250 * 25 = 275_000).

dude.values.inject(1) do |product, number|
  product *= number
  product
end
christmas = {
  santa: "ho ho ho",
  grinch: "presents destroy wealth"
}

Use the christmas hash to construct the following hash: {santa: "ho ho ho"}

christmas.select do |k, v|
  k == :santa
end

The k and v block variables correspond with the keys and values in the christmas hash. To properly follow Ruby conventions, the v block variable should be named _ (underscore) as it is not being used.

What does the following code return? Explain.

blah = {}
sah = blah
sah[:redwall] = "awesome books!"
sah.object_id == blah.object_id
true

blah and sah are initially assigned to the same empty hash. The hash is mutated, but both variables are still assigned to the same object, so they have the same object id.

What does the following code return? Explain.

clocks = {
  big_ben: "tick tock",
  grandfather: "dong dong"
}
big_clocks = clocks.select do |k, _|
  k == :grandfather
end
clocks.object_id == big_clocks.object_id
false

The Hash#select method creates a new hash and does not mutate the original hash. In this example, the big_clocks variable is assigned to a new hash with a different object id.

What does the following code return? Explain.

haha = {a: 1, b: 2}
bozo = haha.merge!({lala: "word up"})
haha.object_id == bozo.object_id
true

The Hash#merge! method combines two hashes and mutates the original hash. Since the haha and bozo variables are assigned to the same object, they have the same object id. If the Hash#merge method was used (notice no !), then the original object would not have been mutated and the object ids would be different.

What does the following code return?

def blah_blah(x, y, chatter)
  chatter.map do |k, v|
    [k, v]
  end
end

blah_blah(34, 22, afi: "high school football hero", against_all_authority: "24 hour roadside resistance")
[[:afi, "high school football hero"], [:against_all_authority, "24 hour roadside resistance"]]

blah_blah() takes three arguments. When a hash is the last argument in a method it does not need to be delimited with {}. The hash can optionally be delimited with {}, so this code will also work:

blah_blah(34, 22, {afi: "high school football hero", against_all_authority: "24 hour roadside resistance"})

Take note of this syntactic sugar as it is frequently used in Ruby gems and Rails.

videos = {yoga: "stretch it out"}

Retrieve the value associated with the :dumb_and_dumber key in the videos hash. If the :dumb_and_dumber key is not available in the hash, return the string "no one's home".

videos.fetch(:dumb_and_dumber, "no one's home")

The second argument to the Hash#fetch method is a default value and is optional. The default value is returned whenever the key is not present in the hash. If a default value is not specified, Hash#fetch will raise an error if the key is not found.

OR

videos[:dumb_and_dumber] || "no one's home"

videos[:dumb_and_dumber] returns nil because the :dumb_and_dumber key is not in the hash. The Ruby || operator returns the second operand if the first operand is falsey (the only falsey values in Ruby are false and nil), so the || operator is another way to set a default value for a hash query.

simpsons = {homer: "my idol"}

What is the difference between the following lines of code?

simpsons.fetch(:bart)
simpsons[:bart]

simpsons.fetch(:bart) raises an exception because the :bart key is not present in the simpsons hash (if a second argument was added to the fetch() method, the default value would be returned instead of raising an exception).

simpsons[:bart] returns nil because the :bart key is not present in the simpsons hash.

Create a hash with a default value of "cheese" so the hash returns the string "cheese" if a key that does not exist in the hash is requested.

my_hash = Hash.new("cheese")
my_hash.empty? # true because there are no elements in my_hash yet
my_hash[:bacon] = "yummy" # add key / value pair to the hash
my_hash[:salad] # returns "cheese" because the :salad key isn't in the hash
my_hash[:bacon] # returns "yummy"

Use the following Person class to create a person object with the first_name "Mohammed" and the age 12.

class Person
  def initialize(args)
    @first_name = args.fetch(:first_name)
    @age = args.fetch(:age)
  end
end
mohammed = Person.new({first_name: "Mohammed", age: 12})

Hashes are great to use as initialization arguments for classes. When hashes are used as initialization arguments, the code is readable and self documenting. Additionally, argument order dependencies are eliminated when hashes are used. When the order of the arguments is changed, the class is still initialized successfully:

mohammed = Person.new({age: 12, first_name: "Mohammed"})
some_hash = {a: 123, b: 456}

Convert some_hash to [[:a, 123], [:b, 456]]
some_hash.map do |k, v|
  [k, v]
end

# OR

some_hash.inject([]) do |memo, (k, v)|
  memo << [k, v]
  memo
end

# OR

some_hash.to_a
h1 = {surfing: "fun"}
h2 = {rock_climbing: "scary"}

Use h1 and h2 to construct the following hash: {:surfing => "fun", :rock_climbing => "scary"}

h1.merge(h2)
singers = {
  kesha: "fun",
  pitbull: "how is this dude famous?",
  missy_elliot: "really smart"
}

Create an array of the values associated with the :kesha and :missy_elliot keys.

singers.values_at(:kesha, :missy_elliot)

# OR

singers.select do |k, v|
  [:kesha, :missy_elliot].include? k
end.values
boring_hash = {}

Return true if the boring hash has no elements and false otherwise.

boring_hash.empty?

# OR

boring_hash.length == 0
people = [["bob", 320], ["edgar", 152], ["maria", 125]]

Convert the people array to the following hash: {"bob" => 320, "edgar" => 152, "maria" => 125}

Hash[people]

# OR

people.inject({}) do |memo, (name, number)|
  memo[name] = number
  memo
end
animals = [['dogs', 4], ['cats', 3], ['dogs', 7]]

Convert animals to {'dogs' => 11, 'cats' => 3}.

animals.inject(Hash.new(0)) do |memo, (animal, num)|
  memo[animal] += num
  memo
end

Notice that a hash with a default value of zero is used as the starting point. If a hash without a default value was used, the code would need to be more complex to account for situations when the key has not been added to the hash yet:

animals.inject({}) do |memo, (animal, num)|
  memo[animal] ? memo[animal] += num : memo[animal] = num
  memo
end

Monkey patch the Hash class and define a method called all_values_even? that returns true if all the values in a Hash are even.

class Hash
  def all_values_even?
    self.values.all?(&:even?)
  end
end

{a: 2, b: 4, c: 6}.all_values_even? # returns true
{a: 3, b: 6}.all_values_even? # returns false

Monkey patching the Hash class allows us to define custom methods that can be applied directly to hash objects.