tmartin314
tmartin314

Reputation: 4171

How to refactor each function with map in Ruby?

I have a loop building a hash for use in a select field. The intention is to end up with a hash:

{ object.id => "object name", object.id => "object name" }

Using:

@hash = {}
loop_over.each do |ac|
  @hash[ac.name] = ac.id
end

I think that the map method is meant for this type of situation but just need some help understanding it and how it works. Is map the right method to refactor this each loop?

Upvotes: 1

Views: 360

Answers (7)

mu is too short
mu is too short

Reputation: 434685

Data transformations like this are better suited to each_with_object:

@hash = loop_over.each_with_object({}) { |ac, h| h[ac.name] = ac.id }

If your brain is telling you to use map but you don't want an array as the result, then you usually want to use each_with_object. If you want to feed the block's return value back into itself, then you want inject but in cases like this, inject requires a funny looking and artificial ;h in the block:

@hash = loop_over.inject({}) { |h, ac| h[ac.name] = ac.id; h }
# -------------------- yuck -----------------------------^^^

The presence of the artificial return value is the signal that you want to use each_with_object instead.

Upvotes: 4

spickermann
spickermann

Reputation: 106932

I would go for the inject version, but use update in the block to avoid the easy to miss (and therefore error prone) ;h suffix:

@hash = loop_over.inject({}) { |h, ac| h.update(ac.name: ac.id) }

Upvotes: 0

Zhomart
Zhomart

Reputation: 742

Ruby 2.1.0 introduces brand new method to generate hashes:

h = { a: 1, b: 2, c: 3 }
h.map { |k, v| [k, v+1] }.to_h # => {:a=>2, :b=>3, :c=>4}

Upvotes: 0

kiddorails
kiddorails

Reputation: 13014

You can simply do this by injecting a blank new Hash and performing your operation:

loop_over.inject({}){ |h, ac| h[ac.name] = ac.id; h }

Ruby FTW

Upvotes: 1

BroiSatse
BroiSatse

Reputation: 44685

Try:

Hash[loop_over.map { |ac| [ac[:name], ac[:id]] }]

Or if you are running on Ruby 2:

loop_over.map { |ac| [ac[:name], ac[:id]] }.to_h

Upvotes: 3

Eyeslandic
Eyeslandic

Reputation: 14900

@hash = Hash[loop_over.map { |ac| {ac.name => ac.id} }.map(&:flatten)]

Edit, a simpler solution as per suggestion in a comment.

@hash = Hash[ loop_over.map { |ac| [ac.name, ac.id] } ]

Upvotes: 1

Martin Konecny
Martin Konecny

Reputation: 59631

No a map isn't the correct tool for this.

The general use-case of a map is to take in an array, perform an operation on each element, and spit out a (possibly) new array (not a hashmap) of the same length, with the individual element modifications.

Here's an example of a map

x = [1, 2, 3, 4].map do |i|
  i+1 #transform each element by adding 1
end

p x # will print out [2, 3, 4, 5]

Your code:

@hash = {}
loop_over.each do |ac|
  @hash[ac.name] = ac.id
end

There is nothing wrong with this example. You are iterating over a list, and populating a hashmap exactly as you wished.

Upvotes: 0

Related Questions