Ruby: How do I use symbols to represent things in an array?
Question
I have an array of arrays that looks like this:
fruits_and_calories = [
["apple", 100],
["banana", 200],
["kumquat", 225],
["orange", 90]
]
I also have a method I want to invoke on each element of the array:
fruits_and_calories.each do |f| eat(f[0], f[1])
I'd really like to be able to say something like:
fruits_and_calories.each do |f| eat(f[:name], f[:calories])
Is there a way that I can pull this off without having to change each item in the array (for example, by iterating through it and somehow adding the symbols in)? Or, if that's too hard, is there a better alternative?
Solution
The best answer is not to use an Array at all, but to use a Hash:
fruits_and_calories = { :apple => 100,
:banana => 200,
:kumquat => 225,
:orange => 90}
fruits_and_calories.each do |name, calories|
eat(name, calories)
end
OTHER TIPS
Without changing the data structure at all, you could change the block arguments to achieve the same thing:
fruits_and_calories.each do |name, calories| eat(name, calories); end
This works because Ruby will auto-expand the inner arrays (["apple", 100], etc) into the argument list for the block ('do |name, calories| ... end'). This is a trick that Ruby inherited from Lisp, known as 'destructuring arguments'.
Pesto's answer (use a hash) is a good one, but I think I'd prefer to use a Struct.
Fruit = Struct.new(:name, :calories)
fruits = [
Fruit.new("apple", 100),
Fruit.new("banana", 200),
Fruit.new("kumquat", 225),
Fruit.new("orange", 90)
]
fruits.each {|f| eat(f.name, f.calories)}
This also lends itself to changing eat
from taking both the name and calories, to taking a fruit instance:
fruits.each {|f| eat(f)}
Is there some reason it must be an array, per se? That seems to cry out to be a hash, or a class for Fruit.
An array is always indexed by numbers, so as far as I know using the standard array it's not possible.
Personally I'd just opt for using a comment above the code to hint what f[0] and f[1] stands for.
But if you are hell bent on doing it I guess some duck-typing on the Array class works:
class Array
def name ; self[0] ; end
def calories ; self[1] ; end
end
# then call it with:
fruits_and_calories.each {|f| eat(f.name, f.calories) }