Enumerable by task

Tasks and their methods

The Enumerable module is the same in arrays, hashes, ranges and files. One method per job, and the job is usually easy to name before the method is:

TaskMethod
Convert every itemmap { }
Keep the items that matchselect { }, reject { }
Split into two listspartition { }
Collapse to one valuereduce(:+), sum
Build a hash or a stringeach_with_object
Find the smallestmin_by { }, max_by { }
Ask a yes-or-no questionany? { }, all? { }
Count matchescount { }

Sum and reduce

sum adds numbers without a block, and with a block it adds the result of the block. That covers most of what reduce was used for:

ruby -e 'p (1..10).sum; p (1..10).select(&:even?).sum'

55
30

reduce is still the method for an operation that is not addition: a product, the longest string, a merge of hashes. partition returns both halves at once, which is cheaper than two select calls:

ruby -e 'p (1..10).partition(&:even?)'

[[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]]

each_with_object

each_with_object passes a memo into the block, so a hash can be built without a variable outside the block. A hash with a default of zero counts in one line:

ruby -e 'p %w[a b a].each_with_object(Hash.new(0)) { |w, h| h[w] += 1 }'

{"a" => 2, "b" => 1}

The memo is the second block argument, which is the opposite of inject, where the accumulator comes first. The argument order is the usual reason the two get mixed; pick each_with_object when the result is a collection and reduce when it is a single value.

What to watch for

map returns an array even when the receiver is a hash: the result is pairs, not a hash. Use to_h after the map, or transform_values when only the values change.

A block with several statements loses the one-line charm and often hides a second job. When a block grows past three lines, name the step as a method and call it from the block.

Next: Sorting and comparison

Article author: Arthur Isaev