Почему это использование метода Hash#each работает только тогда, когда я удаляю оператор splat из параметра?

У меня проблема с Ruby Monk, https://rubymonk.com/learning/books/1-ruby-primer/problems/155-restaurant#solution4804

Их решение великолепно; Мне нравится, компактнее моего. Проблема у меня, я просто не понимаю, почему это работает только тогда, когда я удаляю оператор splat из параметра стоимости orders. Даже если мне не следует поступать таким образом, я изо всех сил пытаюсь понять, в чем дело. Я знаю, иногда нет необходимости во всем разбираться, и лучше просто двигаться дальше... но любопытно.

Вот мой:

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)

Изменить: включить предлагаемое ими решение ниже

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

Изменить: чтобы прояснить и ответить на мой собственный вопрос в комментарии

Я попробовал эти эксперименты, и они показывают, что inject «удаляются» скобки массива, которые «надеваются» на знак. Возможно, это не самый правильный способ думать об этом? Это помогает прояснить мое замешательство.

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

person aymm    schedule 13.02.2014    source источник
comment
Прочтите документацию для Enumerable#inject: он повторяет над элементами в коллекции, по одному за раз. Таким образом, на каждой итерации у вас нет массива, у вас есть только элемент из массива. Он удаляет массив так же, как each, обрабатывая содержимое по одному элементу за раз.   -  person Phrogz    schedule 13.02.2014
comment
Большое спасибо @Phrogz, теперь это имеет смысл.   -  person aymm    schedule 13.02.2014


Ответы (1)


Когда вы пишете:

def cost(*orders)
end

тогда все параметры, переданные методу cost, будут помещены в один массив с именем orders. Таким образом, эти два эквивалентны:

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

В вашем случае, когда вы удаляете "знак", вы говорите "установить orders для ссылки на то, что было передано напрямую". В этом случае вы передаете ему хэш, и когда вы повторяете хэш, вы получаете пары ключ/значение для каждой записи. Это именно то, что вы хотите.

Однако, когда у вас есть значок, вы получаете следующее:

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

Итак, вы оборачиваете свой хеш в массив, а затем выполняете итерацию по элементам массива. Таким образом, первое переданное в блок значение — это весь хеш, а второго параметра нет.

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)

На данный момент вы ничего не можете умножить на nil, поэтому ваш код ломается.

person Phrogz    schedule 13.02.2014
comment
Хорошо, это многое объясняет. Что касается предложенного ответа rubymonk, удаляет ли использование inject охватывающий массив? Я имею в виду, почему звонит то, что кажется: p [{:rice => 1, :noodles => 1}].keys #=> rice \n noodles - person aymm; 13.02.2014