Creating a Hash from a Collection
I recently needed to create a hash lookup from an object using the ID as the lookup key and the full object as the value in our rails app. It wasn’t a difficult concept and there were several ways this could be accomplished; however, we just couldn’t find a way that made sense or didn’t involve looping. That is, until we found index_by
.
Setup
Let’s assume we have an Animal
class and an Animals
list class:
class Animal
def initialize(id, name)
@id = id
@name = name
end
attr_reader :id, :name
end
class Animals
def initialize(animals)
@animals = animals
end
def animal_list
animals.each_with_index.map { |animal, i| Animal.new(i, animal) }
end
attr_reader :animals
end
So we’d have a list of animals:
animal_array = ['King Mukla', 'Misha', 'Leokk', 'Huffer']
animals = Animals.new(animal_array).animal_list
=> [
[0] #<Animal:0x007fde9678e098 @id=0, @name="King Mukla">,
[1] #<Animal:0x007fde9678e070 @id=1, @name="Misha">,
[2] #<Animal:0x007fde9678e048 @id=2, @name="Leokk">,
[3] #<Animal:0x007fde9678e020 @id=3, @name="Huffer">
]
Using Hash[a]
Our first solution took advantage of Hash
:
hash = animals.map { |animal| [animal.id, animal] }
Hash[hash]
This was our default approach in conversion in the past, but creating an array of arrays just to use this approach seemed lengthy and unnecessary.
Using Enumerable .reduce()
Our second solution took advantage of the reduce method in the Enumerable Class:
animals.reduce({}) { |hash, animal|
hash[animal.id] = animal
hash
}
This is slightly better, since we don’t have to map on the array only to convert; but needing to return the hash at the end isn’t ideal either. Both solutions so far would usually require a doc lookup for .reduce and Hash[a].
Using Enumerable .index_by()
Our final solution, we used the Enumerable index_by
:
animals.index_by(&:id)
Note: .index_by
accepts a block. So if you want to index by two fields such as first and last name, you could easily do this with the block:
people.index_by { |person| "#{person.first_name} #{person.last_name}" }
All three solutions yield the same result; a hash with the keys being the ID of the object:
{
0 => #<Animal:0x007fde935ce530 @id=0, @name="King Mukla">,
1 => #<Animal:0x007fde935ce440 @id=1, @name="Misha">,
2 => #<Animal:0x007fde935ce418 @id=2, @name="Leokk">,
3 => #<Animal:0x007fde935ce3f0 @id=3, @name="Huffer">
}