I have two hashes:
For example, one contains a list of dishes and their prices
dishes = {"Chicken"=>12.5, "Pizza"=>10, "Pasta"=>8.99}
The other is a basket hash i.e. I've selected one pasta and two pizzas:
basket = {"Pasta"=>1, "Pizza"=>2}
Now I am trying to calculate the total cost of the basket but can't seem to get my references right.
Have tried
basket.inject { |item, q| dishes[item] * q }
But keep getting the following error
NoMethodError: undefined method `*' for nil:NilClass
Let's look at the documentation for
Enumerable#inject
to see what is going on.inject
"folds" the collection into a single object, by taking a "starting object" and then repeatedly applying the binary operation to the starting object and the first element, then to the result of that and the second element, then to the result of that and the third element, and so forth.So, the block receives two arguments: the current value of the accumulator and the current element, and the block returns the new value of the accumulator for the next invocation of the block. If you don't supply a starting value for the accumulator, then the first element of the collection is used.
So, during the first iteration here, since you didn't supply a starting value for the accumulator, the value is going to be the first element; and iteration is going to start from the second element. This means that during the first iteration,
item
is going to be['Pasta', 1]
andq
is going to be['Pizza', 2]
. Let's just run through the example in our heads:Ergo, you get a
NoMethodError
.Now, I believe, what you actually wanted to do was something like this:
Now, while
inject
is capable of summing (in fact,inject
is capable of anything, it is a general iteration operation, i.e. anything you could do with a loop, you can also do withinject
), it is usually better to use more specialized operations if they exist. In this case, a more specialized operation for summing does exist, and it is calledEnumerable#sum
:But there is a deeper underlying problem with your code: Ruby is an object-oriented language. It is not an array-of-hash-of-strings-and-floats-oriented language. You should build objects that represent your domain abstractions:
Now, of course, for such a simple example, this is overkill. But I hope that you can see that despite this being more code, it is also much much simpler. There is complex navigation of complex nested structures, because a) there are no complex nested structures and b) all the objects know for how to take care of themselves, there is never a need to "take apart" an object to examine its parts and run complex calculations on them, because the objects themselves know their own parts and how to run calculations on them.
Note: personally, I do not think that allowing arithmetic operations on
Dish
es is a good idea. It is more of a "neat hack" that I wanted to show off in this code snippet.Try this one
one line
Your variables for the block are wrong. You have the accumulator and an item (that it's an hash)
With Ruby 2.4, you could use
Hash(Enumerable)#sum
with a block :Data structure
dishes
dishes
(what I calledprices
to avoid writingdishes[dish]
) is the correct data structure :basket
basket
is also fine as a Hash, but only if you don't oder any dish more than once. If you want to order 2 pizzas, 1 pasta and then 3 pizzas again :you'll lose the first order.
In that case, you might want to use an array of pairs (a 2-element array with
dish
andquantity
) :With this structure, you could use the exact same syntax to get the total as with a Hash :