Ruby iterators - collect, select, inject, reject, detect and friends (part I)
a.k.a. Blogging as a way to solidify knowledge and/or opinion.
a.k.a. Sometimes you gotta write things down to remember them.
Lets talk about iterators. These are the bread and butter of ruby. There is each, and then there are the lovely -ects. Reject. Select. Collect. Strong powerful verbs.
You typically call iterators with a block.
A block is either a chunk of code between curly brackets {} or a chunk of code between a do ... end
Iterators in RUBY (psst, not rails specific)
Yes, plain jane ruby. No fancy extensions by Rails. Hold your breath!
map and collect are the same thing.
map and collect return an array that contains the results of running the block with each item.
1 2 |
singles = [1,2,3,4] doubles = singles.collect { |i| i+i } #=> [2, 4, 6, 8] |
Get it? Lovely. For PHP heads: It's like you went to the trouble to initialize an array, build a for loop that goes to each number, doubles it, and adds it to the new array. Only without the work ;)
But maybe you want to modify the array in-place?
Simple. Bang!
1 2 |
[1,2,3,4].collect! { |i| i+i } #=> [2, 4, 6, 8] |
Use each to get fancy iterating
Let's say you want to do something more interesting with your array. You don't want to just create another array from going through each item, you want to do something more fancy.
1 2 3 |
story = "" ['once','upon','a','time'].each {|word| story << word << " "} |
select and find_all are the same thing
select can be confusing as a name. It just means "go through and select out the elements where my block is true." Maybe you have a basket of fruit, and you only want citrus. Assuming your fruits have a is_citrus? method, use select to get the tangy ones:
1 2 |
[Banana, Apple, Orange, Lemon].select { |fruit| fruit.is_citrus? } #=> [Orange, Lemon] |
reject
Reject is what you don't select - the opposite of select.
In other words, it'll "go through and select the elements where the block is false". So, your tummy is a bit on edge, and you are looking for fruits without too much citric acid:
1 2 |
[Banana, Apple, Orange, Lemon].reject { |fruit| fruit.is_citrus? } #=> [Banana, Apple] |
inject
An annoying named method, perhaps - the most powerful and typically last-learned of the ruby iterators. Inject? Like injection?
You can think of Inject as being the action and the result being an accumulation or gathering of what happens in the block.
Let's say you want to know the weight of your fruit basket:
1 2 |
[Banana, Apple, Orange, Lemon].inject{|total_weight, fruit| total_weight += fruit.weight } |
Inject also takes a default value for the accumulator (in this case, total_weight). So, let's say we want to weigh our fruits AND our basket:
1 2 |
[Banana, Apple, Orange, Lemon].inject(100){|total_weight, fruit| total_weight += fruit.weight } |
That will take 100 as the starting weight, go through each fruit, weigh it, and add it to the total.
Maybe the easiest way to remember Inject is "you have an extra variable to play with in the block which is returned to you at the end of the iterating"
To do the fruits example without inject would take this:
1 2 3 4 |
total_weight = 100 [Banana, Apple, Orange, Lemon].each {|fruit| total_weight += fruit.weight} total_weight |