cft

6 Advanced Ruby Loops

Ruby's toolbox has everything you need


user

Benny Sitbon

3 years ago | 5 min read

(Originally published on Medium)

Over the last few years, I’ve been writing a lot of Ruby. The language has earned a special place in my heart due to its “optimizing for developer happiness” principle.

It’s a language that is easy to read, write, onboard, template, and prototype, among other benefits.

One of its best features is providing helper methods to many commonly-used procedures. Iterating, sorting, filtering, transforming, and others all have multiple helper methods built-in to the language (in the enumerable module).

You can focus on your logic more, and less on writing helper methods that iterate over enumerables.

Not only does this make code a lot more fun to write, but it also makes the code more readable.

These helper methods abstract the details of how the iteration happens, and give the stage to logic itself. Since code is read much more often than it is written, it also makes for more efficient coding.

In an effort to share some “developer happiness”, I’d like to go over several less-known Ruby loops. These will make your code less verbose, easier to read, and quicker to write:

  1. map
  2. each_with_object
  3. each_with_index
  4. partition
  5. select/reject
  6. any?/all?
  7. Bonus: find_index

Please note: I’m using an array of numbers in most examples here for simplicity. But all these methods will work both for arrays and hashes. Both are considered enumerables.

map

Useful for creating an array out of an enumerable and applying certain logic for each element.

For example, squaring each value in an array of numbers. So, instead of using each to iterate and insert the result to another array, you can just use map.

Code example:

# ok: Instead of using each and appending to a var

def build_array(nums)

result = []

nums.each do |num|

result << calculate(num)

end

result

end


# better: You can just use map

def build_array(nums)

result = nums.map { |num| calculate(num) }

end


# best: Or even shorter.

def build_array(nums)

result = nums.map(&:calculate)

end

# Actually, we can skip this method and just inline `result = nums.map(&:calculate)`.


each_with_object

Useful for creating an object by iterating over an enumerable.

For example, what if we wanted to produce a hash from an array? Or a Person object from a given hash?

You could also use the reduce method, but I find its syntax confusing, so I prefer each_with_object.

Pro tip: Unlike map, you can add ifs to the block and pick and choose which elements to run the logic on.

Code example:

# ok: you can use an instantiated hash, which isnt pretty

def build_hash(nums)

h = {}

nums.each { |num| h[num] = calculate(num) }

h

end


# better: or you can use each_with_object

def build_hash(nums)

# it receives the object to start with ({}) and accumulates on top of it

nums.each_with_object({}) { |num, res| res[num] = calculate(num) }

end


# bonus: works with any kind of object

def build_array(nums)

# you can replace the accumulator to be an array, or any object really.

nums.each_with_object([]) { |num, res| res << calculate(num) }

end


each_with_index

Useful when, in addition to iterating over an enumerable, you also need to use the index. For example, what if we need to log the current index we’re processing?

Code example:

# ok: Let's print a status log before each iteration

def handle(nums)

num_count = nums.count

# we can use the count to iterate

for i in 0..num_count do

Rails.logger.info("Handling item #{i}/#{num_count}")

do_something(nums[i])

end

end


# better: much more readable

def handle(nums)

nums.each_with_index do |num, i|

Rails.logger.info("Handling item #{i}/#{nums.count}")

do_something(num)

end

end

partition

Imagine you could, in one line, split an array into two arrays, based on a condition. Now stop imagining, you can do exactly that with partition.


Code example:

# ok: let's split an array to an odd array and even array

def split_odds_evens(nums)

odds = []

evens = []

nums.each do |num|

if num.odd?

odds << num

else

evens << num

end

end

[odds, evens]

end


# better: ruby helps you out! So simple, just inline it

def odd_even_short(nums)

nums.partition(&:odd?)

end

select/reject

select iterates over an enumerable and returns only the elements that answer the given block. rejects acts the same but opposite, it returns the ones that don’t answer the condition:

For arrays, select and reject are available in ! (bang) flavors as well: select! and reject!. These will modify the given enumerable (! is often used to signal a destructive operation), so be careful when using it.

Code example:

### select ###

# ok: get a new array with only odd numbers

def only_odds(nums)

result = []

nums.each do |num|

result << num if num.odd?

end

result

end


# better: shorter and cleaner

def only_odds_object(nums)

nums.each_with_object([]) do |num, arr|

arr << num if num.odd?

end

end


# best: so simple you can just inline it

def only_odds_simple(nums)

nums.select(&:odd?)

end


### reject ###

# exact opposite of `select`

def only_odds(nums)

nums.reject(&:even?)

end

any?/all?

any? checks that at least one element in an enumerable corresponds to a condition. all? verifies that all elements fit the bill.

Code example:

### all? ###

# ok: checking if all nums are odd

def all_odds?(nums)

nums.each do |num|

return false if num.even?

end

true

end


# better: so simple you don't even really need a method, just inline it

def easier_all_odds?(nums)

nums.all?(&:odd?)

end


### any? ###

# ok: checking if any of the numbers are greater than 0

def any_greater_than?(nums, x)

nums.each do |num|

return true if num > x

end

return false

end


# better: so simple, it might be better to just write it inline

def easier_any_greater_than?(nums, x)

nums.any? { |num| num > x }

end

Bonus: find_index

Sometimes, you just need to find the index of an element matching a condition.

Code example:

# ok: looking for an index of a specific object - explicit solution

def find_num(nums, x)

nums.each_with_index do |num, i|

return i if num == x

end

nil

end


# better: so simple, just write it inline

def find_num(nums, x)

nums.find_index(num)

end


# this actually works with blocks too. This would work for all object

# just need the right coparer func

def find_obj(array, obj)

array.find_index{ |element| yield(element, obj) }

end


# call it like this:

# find_obj([1,2,3], 2) { |x, y| x == y }

# => 1

To Iterate the Topic One Last Time

  1. If you need a special way to iterate over enumerables in Ruby, there’s a good chance it already exists. If you use a framework (like Rails), you might have even more methods, as they include gems that expand on the base collection.
  2. The above helper methods help you write more concise and focused code, therefore making it easier for a human to read.

Thanks for reading!

Upvote


user
Created by

Benny Sitbon


people
Post

Upvote

Downvote

Comment

Bookmark

Share


Related Articles