high level iterators

4 Min. Read
Oct 14, 2019

High Level Iterators to replace ForEach statement in your code:

Every occurrence of a ForEach iterator in a code can be replaced by a high level, specialized iterator. As we have experienced, we are rarely interested in only iterating over the collection. Most of the times, our goal will be to return an aggregation or construction of a new value and returning that value, or a new collection computed from the original collection, rather than simply iterating over the collection.

Command-Query Separation:

Command-Query separation exactly replicates this scenario. Commands are methods that mutate state and don’t return value. Query methods on the other hand are methods that return a certain value and do not mutate the state of our application. Query methods are when a method is asking a question, and we can retrieve a value back from the question, a command method, however, is when a method is performing an action. Classic forEach iterator will suffice in case of command methods, whereas, it is a good practice to use a high level iterator whenever implementing query methods.

High level iterators make us go from “Initializing an empty array, then iterating over another array and adding the current element of this array to the first array if it is divisible by two without a remainder” to just simply “Selecting all even numbers”.

Some high level methods to replace classic iteration, in Ruby are:

  • transform each element in some way: map,
  • reduce the whole collection down to some single value, e.g. computing the sum or the average or the standard deviation of a list of football scores: inject
  • filter out all elements that satisfy a certain condition: filter, select, find_all,
  • filter out all elements that do not satisfy a condition: reject
  • find the first element that satisfies a condition: find
  • count the elements that satisfy a condition: count
  • check if all elements (all?), at least one element (any?) or no elements (none?) satisfy a condition
  • group the elements into buckets based on some discriminator: group_by
  • partition the collection into two collections based on some predicate: partition
  • sort the collection: sort, sort_by, etc

Map:

Ruby’s map method can be used with Arrays, Hashes and Ranges. Map method transforms data. Map returns a new array with the results, leaving the original array untouched. Eg:

1
2
array = [1,2,3]
array.map { |n| n * 2 }

Inject:

inject takes a value to start with (the 0 in your example), and a block, and it runs that block once for each element of the list.

  • On the first iteration, it passes in the value you provided as the starting value, and the first element of the list, and it saves the value that your block returned (in this case result + element).
  • It then runs the block again, passing in the result from the first iteration as the first argument, and the second element from the list as the second argument, again saving the result.
  • It continues this way until it has consumed all elements of the list.
1
2
3
4
inject (value_initial) { |result_memo, object| block }

[1, 2, 3, 4].inject(0) { |result, element| result + element }
# => 10

This can be analyzed in simple steps as:

Step :1 [1, 2, 3, 4].inject(0) { |0, 1| 0 + 1 }

Step :2 [1, 2, 3, 4].inject(0) { |1, 2| 1 + 2 }

Step :3 [1, 2, 3, 4].inject(0) { |3, 3| 3 + 3 }

Step :4 [1, 2, 3, 4].inject(0) { |6, 4| 6 + 4 }

Step :5 [1, 2, 3, 4].inject(0) { |10, Now no elements left in the array, so it’ll return 10 from this step| }

Similarly,

1
[1, 2, 3, 4].inject(5) { |result, element| result + element } # => 15

Select: Select method in ruby filters through the collection of objects. Using select requires a block.

Inside the block, we have to return something that evaluates to true or false, and select will use that to filter our collection.

Eg:

1
2
3
4
[1,2,3,4,5,6].select { |n| n.even? }

[1,2,3,4,5,6].select(&:even?)

Find:

Select returns a collection with all the results. We can use find to filter through a list and return a first match, or nil if it cannot be found.

1
2
3
4
5
6
7
letters = %w(a aa aaa aaaa)

letters.find { |l| l.size == 3 }
# "aaa"

letters.find { |l| l.size == 10 }
# nil

Reject:

It is the opposite of select.

Eg:

1
2
[1,2,3,4,5,6].reject { |n| n == 4 }
# => [1,2,3,5,6]

Partition:

The partition method calls the given block for each element in the collection, and separates them into items that returned true, and those that didn’t. It returns them in two arrays.

Eg:

1
2
3
4
5
6
7
8
9
10
11
12
[1,2,3,4,5].partition(&:even?)
 # => [[2, 4], [1, 3, 5]]

pet_lists = inventory.partition{|pet| pet.legs == 4}

# pets with exactly four legs:
pet_lists[0].map(&:name)
    # => ["dog", "cat"]

# pets without exactly four legs:
pet_lists[1].map(&:name)
    # => ["fish", "scorpion", "beetle", "monkey", "rock"]

Partition method in String:

Partition method in string uses regex to search for a match in a string. If it finds one, it returns what came before the match, the match, and what came after as elements in a single array.

Eg:

1
2
'foo bar baz bat'.partition 'bar'
 # => ["foo ", "bar", " baz bat"]