Idiomatic Ruby replaces most hand-written loops with one well-chosen Enumerable method.
Enumerable is a module of 50+ methods available on any class that defines each, including Array, Hash, Range, Set and your own collections. Knowing the catalogue is what makes Ruby code short.
- Aggregation:
sum,count,min_by,max_by,minmax, andreduce(:+)orinject(0) { |acc, x| acc + x }for anything custom. - Grouping:
group_by { |w| w.length }returns a hash of arrays;partitionsplits into two arrays (matches, non-matches);tallycounts occurrences;chunk_whileandslice_whensplit runs. - Building:
each_with_object({}) { |x, h| ... }passes the same accumulator to every call, andto_h { |x| [key, value] }builds a hash directly. - Transforming:
flat_map,filter_map(map and drop nils/falses in one pass),zip,each_slice(n),each_cons(n),uniq. - Ordering:
sort_by { |u| [u.last, u.first] }sorts by several keys;sortuses<=>;max(3)returns the three largest.
Hashes enumerate [key, value] pairs, so blocks can take |k, v|. They also have hash-returning versions: transform_values, transform_keys, filter_map, select, sum, to_h.
When a method takes a block and you call it without one, many return an Enumerator, so you can chain: each_with_index.map { |x, i| ... } or map.with_index(1).
Example
words = %w[apple bob cat avocado banana cherry bob]
p words.tally # {"apple" => 1, "bob" => 2, "cat" => 1, ...}
p words.group_by(&:size) # {5 => ["apple"], 3 => ["bob", "cat", "bob"], ...}
short, long = words.partition { it.size <= 3 }
p short # ["bob", "cat", "bob"]
index = words.uniq.each_with_object(Hash.new { |h, k| h[k] = [] }) do |w, h|
h[w[0]] << w
end
p index["a"] # ["apple", "avocado"]
p words.uniq.sort_by { [-it.size, it] }.first(2) # ["avocado", "banana"]
p (1..10).reduce(:*) # 3628800
p [1, nil, 3].filter_map { it && it * 2 } # [2, 6]
p words.map.with_index(1) { |w, i| "#{i}.#{w}" }.first(2) # ["1.apple", "2.bob"]
prices = { tea: 3, cake: 5 }
p prices.transform_values { it * 2 } # {tea: 6, cake: 10}
p prices.sum { |_, v| v } # 8Remember
- Any class with each can include Enumerable and get 50+ methods
- group_by, partition, tally and each_with_object replace manual loops
- Block-less calls return Enumerators, enabling map.with_index and chaining
Watch out for
- Using inject with a mutable accumulator but forgetting to return it from the block; use each_with_object instead.