When I started writing Ruby many years ago, it took me a while to understand the difference between each and map. It only got worse when I discovered all the other Enumerable and Array methods.
With the help of the official documentation and many StackOverflow questions, I slowly began to understand what those methods did.
Here is what took me even longer to understand though :
- Why should I use one method or another?
- Are there any guidelines?
I hope this question isn't a duplicate : I'm more interested in the "Why?" than the "What?" or "How?", and I think it could help Ruby newcomers.
A more tl;dr answer:
Use
#each
when you want "generic" iteration and don't care about the result. Example - you have numbers, you want to print the absolute value of each individual number:Use
#map
when you want a new list, where each element is somehow formed by transforming the original elements. Example - you have numbers, you want to get their squares:Use
#inject
when you want to somehow reduce the entire list to one single value. Example - you have numbers, you want to get their sum:Use
#each_with_index
in the same situation as#each
, except you also want the index with each element:Uses for
#each_with_object
are more limited. The most common case is if you need something similar to#inject
, but want a new collection (as opposed to singular value), which is not a direct mapping of the original. Example - number histogram (frequencies):Which object can I use?
First, the object you're working with should be an Array, a Hash, a Set, a Range or any other object that respond to
each
. If it doesn't, it might be converted to something that will. You cannot calleach
directly on a String for example, because you need to specify if you'd like to iterate over each byte, character or line.I want to calculate something with each element, just like with a for loop in C or Java.
If you want to iterate over each element, do something with it and not modify the original object, you can use
each
. Please keep reading though, in order to know if you really should.It is the most generic iteration method, and you could write any of the other mentioned methods with it. We will actually, for pedagogical purposes only. If you spot a similar pattern in your code, you could probably replace it with the corresponding method.
It is basically never wrong to use
each
, it is almost never the best choice though. It is verbose and not Ruby-ish.Note that
each
returns the original object, but this is rarely (never?) used. The logic happens inside the block, and should not modify the original object.The only time I use
each
is:I want to get an Array out of my String/Hash/Set/File/Range/ActiveRecord::Relation
Just call
object.to_a
.Some methods described below (e.g.
compact
,uniq
) are only defined for Arrays.I want to get a modified Array based on the original object.
If you want to get an Array based on the original object, you can use
map
. The returned object will have the same size as the original one.The returned Array will not replace the original object.
This method is very widely used. It should be the first one you learn after
each
.collect
is a synonym ofmap
. Make sure to use only one of both in your projects.I want to get a modified Hash based on the original Hash.
If your original object is a Hash,
map
will return an Array anyway. If you want a Hash back :I want to filter some elements.
I want to remove nil elements
You can call
compact
. It will return a new Array without the nil elements.I want to write some logic to determine if an element should be kept in the new Array
You can use
select
orreject
.I want to remove duplicate elements from your Array
You can use
uniq
:I want to iterate over all the elements while counting from 0 to n-1
You can use
each_with_index
:each_with_index
returns the original object.I want to iterate over all the elements while setting a variable during each iteration and using it in the next iteration.
You can use
inject
:It returns the variable as defined by the last iteration.
reduce
is a synonym. As with map/collect, choose one keyword and keep it.I want to iterate over all the elements while keeping a variable available to each iteration.
You can use
each_with_object
:It returns the variable as modified by the last iteration. Note that the order of the two block variables is reversed compared to
inject
.If your variable is a Hash, you should probably prefer this method to inject, because
h["a"]=1
returns 1, and it would require one more line in your inject block to return a Hash.I want something that hasn't been mentioned yet.
Then it's probably okay to use
each
;)Notes :
It's a work in progress, and I would gladly appreciate any feedback. If it's interesting enough and fit in one page, I might extract a flowchart out of it.