Why does this use of the Hash#each method work only when I remove the splat operator from the parameter?

StackOverflow https://stackoverflow.com/questions/21744447

  •  10-10-2022
  •  | 
  •  

Domanda

I'm going through a problem on Ruby Monk, https://rubymonk.com/learning/books/1-ruby-primer/problems/155-restaurant#solution4804

Their solution is great; I like it and it's more compact than mine. Problem is for mine, I just don't understand why it only works when I remove the splat operator from the cost parameter orders. Even if I shouldn't be doing it this way, I'm struggling to figure out what's up. I know sometimes it's unnecessary to understand everything, and it's best to just move on.. but curious.

Here is mine:

class Restaurant
  def initialize(menu)
    @menu = menu
  end

  def cost(*orders)
    total_cost = 0
    orders.each do |item, number|
    total_cost += @menu[item] * number
  end
end

menu = {:rice => 3, :noodles => 2}
orders = {:rice => 1, :noodles => 1}
eat = Restaurant.new(menu)
puts eat.cost(orders)

Edit: To include their suggested solution below

class Restaurant
  def initialize(menu)
    @menu = menu
  end

  def cost(*orders)
    orders.inject(0) do |total_cost, order|
      total_cost + order.keys.inject(0) {|cost, key| cost + @menu[key]*order[key] }
    end
  end
end

Edit: To clear up and answer my own question in the comment

I tried these experiments and it shows inject "removing" the array brackets that splat "put on". Perhaps not the most proper way to think about it? It does help clear up my confusion.

order = { :rice => 1, :noodles => 1 }
menu = { :rice => 3, :noodles => 2 }

[order].inject(0) do |bla, blu|
    p bla       #=> 0
    p blu       #=> {:rice=>1, :noodles=>1}
    p blu.keys  #=> [:rice, :noodles]
end
È stato utile?

Soluzione

When you write:

def cost(*orders)
end

then all the parameters passed to the cost method will be put into a single array named orders. These two are thus equivalent:

def cost(*orders)
  p orders.class #=> Array
  p orders       #=> [1,2,3]
end
cost(1,2,3)

def cost(orders)
  p orders.class #=> Array
  p orders       #=> [1,2,3]
end
cost( [1,2,3] )     # note the array literal brackets

In your case, when you remove the "splat" you are saying "set orders to reference whatever was passed in directly". In this case you're passing it a Hash, and when you iterate a hash you get key/value pairs for each entry. This is just what you want.

When you do have the splat, though, you're getting this:

def cost(*orders)
  p orders.class #=> Array
  p orders       #=> [{:rice=>1, :noodles=>1}]
end
orders = {:rice=>1, :noodles=>1}
cost(orders)

So you're wrapping your hash in an array, and then iterating over the elements of the array. Thus, the first value passed to the block is the entire hash, and there is no second parameter.

def cost(*orders)
  p orders.class #=> Array
  p orders       #=> [{:rice=>1, :noodles=>1}]
  orders.each do |item,number|
    p item       #=> {:rice=>1, :noodles=>1}
    p number     #=> nil
  end
end
orders = {:rice=>1, :noodles=>1}
cost(orders)

At this point you can't multiply anything by nil and so your code breaks.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top