Herbert Pan
Herbert Pan

Reputation: 11

Unable to use .each to for hash value operation

Why can't I combine newer to older?

def update_inventory(older, newer)
  newer.each {|x, y| older[x] += y}
end

update_inventory({rubies: 10, emeralds: 14, diamonds: 2}, {emeralds: 27, moonstones: 5})

undefined method +' for nil:NilClass
(repl):2:in block in update_inventory
(repl):2:in each
(repl):2:in update_inventory
(repl):5:in <main>

Upvotes: 0

Views: 56

Answers (1)

Stefan
Stefan

Reputation: 114158

It doesn't work as expected because older does not contain all keys from newer.

newer consists of two key-value pairs: {emeralds: 27, moonstones: 5} so newer.each will invoke the block { |x, y| ... } twice:

  1. on the 1st iteration, x is :emeralds and y is 27 which works just fine because older has a value for :emeralds:

    older[:emeralds]       #=> 14
    older[:emeralds] += 27 #=> 41
    
  2. on the 2nd iteration, x is :moonstones and y is 5, so you get:

    older[:moonstones]      #=> nil
    older[:moonstones] += 5 #=> NoMethodError: undefined method `+' for nil:NilClass
    

    The error is caused because Ruby tries to calculate nil + 5 and nil doesn't have a + method (hence "undefined method `+' for nil:NilClass").


To fix this, you could add a condition that checks whether the key exists. But there's a simpler way: Hash#update does exactly what you need:

def update_inventory(older, newer)
  older.update(newer) { |_, v1, v2| v1 + v2 }
end

It will copy the key-value pairs from newer into older. If a key exists in both hashes, the block will be called to determine the new value.

older = {rubies: 10, emeralds: 14, diamonds: 2}
newer = {emeralds: 27, moonstones: 5})

older.update(newer) { |_, v1, v2| v1 + v2 }
#=> {:rubies=>10, :emeralds=>41, :diamonds=>2, :moonstones=>5}

Upvotes: 5

Related Questions