# Collection patterns

## Where do they come from?

Extracted from repetitive code.
Accepting the part that varies as an argument.

``````
def convert_elements_to_symbols(elements)
symbols = []
elements.each{|el| symbols << el.to_sym}
symbols
end

def transform_to_percentage(ratios)
[].tap do |percentages|
ratios.each{|ratio| percentages << ratio * 100}
end
end
``````

## Why should I use them?

• Common vocabulary
• Less code
• Better at declaring intent
• Keeps things at same level of abstraction

## Format

• Name, Aliases, and Library
• Problem
• Pattern

## The RHYMING Five

(and a close relative)

collect
select
detect
reject
inject
each_with_object

(aka collect)

## Problem

I have a collection of items
that I want to convert into
an array of some related set
of items.

## Pattern

``````def convert_elements_to_symbols(elements)
symbols = []
elements.each{|el| symbols << el.to_sym}
symbols
end``````
````class Array``  def map``    collection = []``    each{|item| collection << yield(item)}``    collection``  end``end````def convert_elements_to_symbols(elements)
elements.map(&:to_sym)````end````

(aka find_all)

## Problem

I want to find all the items
in my collection that meet
a particular criteria.

## Pattern

``def extract_even_numbers(numbers)``  even_numbers = []``  numbers.each{|number| even_numbers << number if number.even?}``  even_numbers``end``
``class Array``  def select``    items = []``    each{|item| items << item if yield(item)}``    items``  end``end``def extract_even_numbers(numbers)``  numbers.select(&:even?)``end``

(aka find)

## Problem

I want to find the first item
in my collection that meet
a particular criteria.

## Pattern

``def find_user_by_email(users, email)``  users.each do |user|``    return user if user.email == email``  end``  nil``end``
``class Array``  def detect``    each{|item| return item if yield(item)}``    nil``  end``end``def find_user_by_email(users, email)``  users.detect{|user| user.email == email}``end``

## Bonus

You can also pass a lambda, whose value will be returned when nothing is found.

``%w(a b c).detect(->{'default'}) do |i|``  i == 'd'``end #=> "default"``

## Reject

(aka filter, but not in ruby)

## Problem

I have a list of items where
I want filter out some items
based on a particular criteria.

## Pattern

``def names_without_a(names)``  aless_names = []``  names.each do |name|``    aless_names << name unless name =~ /a/i``  end``  aless_names``end``
``class Array``  def reject``    select{|item| !yield(item)}``  end``end``def names_without_a(names)``  names.reject{|name| name =~ /a/i}``end``

(aka reduce)

## Problem

I have a list of items
I want to aggregate
into a single value.

## Pattern

``def total_adjustment_amount(adjustments)``  total = 0``  adjustments.each{|adjustment| total += adjustment}``  total``end``
``class Array``  def inject(start)``    accumulator = start``    each{|item| accumulator = yield(accumulator, item)}``    accumulator``  end``end``def total_adjustment_amount(adjustments)``  adjustments.inject(0){|accu, adjustment| accu + adjustment}``end``

## each_with_object

(aka with_object)

## Problem

I have a list of items
that I want to use to
mutate an object.

## Pattern

``def create_identity_map(users)``  map = {}``  users.each{|user| map[user.id] = user}``  map``end``
``class Array``  def each_with_object(object)``    each{|item| yield(object, item)}``    object``  end``end``def create_identity_map(users)``  users.each_with_object({}){|map, user| map[user.id] = user}``end``

## Inject vs Each_with_object

Use each_with_object when mutating
mutable objects. Use inject when accumulating
immutable values. When possible, prefer
treating values as immutable and use inject.

all?
any?
include?

Skipped
none?
one?

## Problem

You have a list of
items for which you
want to check if a
condition is true for
all of them.

## pattern

``def everyone_over_18?(teens)``  teens.each{|teen| return false unless teen.over_18?}``  true``end``
``class Array``  def all?``    each{|item| return false unless yield(item)}``    true``  end``end``def everyone_over_18?(teens)``  teens.all?(&:over_18?)``end``

## Problem

You have a list of
items for which you
want to check if a
condition is true for
any of them.

## Pattern

``def anyone_over_18?(teens)``  teens.each{|teen| return true if teen.over_18?}``  false``end``
``class Array``  def any?``    each{|item| return true if yield(item)}``  end``end``def anyone_over_18?(teens)``  teens.any?(&:over_18?)``end``

(aka member?)

## Problem

You want to check for
the presence of an item
in a list.

## Pattern

``def valid_name?(name)``  VALID_NAMES.each{|valid_name| return true if valid_name == name}``  false``end``
``class Array``  def include?(item)``    any?{|member| member == item}``  end``end``def valid_name?(name)``  VALID_NAMES.include?(name)``end``

sort
sort_by

Skipped
max
max_by
min
min_by
minmax
minmax_by

## Problem

You have an unordered
set of items, that you want
ordered based on some criteria.

## Pattern

```` def order_by_age(people)````  return people if people.size <= 1 # already sorted
swapped = true
while swapped do
swapped = false
0.upto(people.size-2) do |i|
if people[i].age > people[i+1].age
people[i], people[i+1] = people[i+1], people[i] # swap values
swapped = true
end
end
end

people````end````

## Pattern

``class Array``  def sort``    mergesort``  end``end``def order_by_age(people)``  people.sort{|alice, bob| bob.age <=> alice.age}``end``

## Problem

You have a list of items
you want to order by a
specific attribute

## Pattern

``class Array``  def sort_by``    sort{|a, b| yield(a) <=> yield(b)}``  end``end``def order_by_age(people)``  people.sort_by(&:age)``end``

flatten
group_by

Skipped
partition
chunk

## Problem

You have a list of lists that you
want in a single list.

## Pattern

``def combine_lists(lists)``  full_list = []``  lists.each do |list|``    full_list.concat(list)``  end``  full_list``end``
``class Array``  def flatten``    each_with_object([]){|list, item| list.concat(item)}``  end``end``def combine_lists(lists)``  lists.flatten``end``

## Problem

You want to group items
in a list by an attribute of
those items.

## Pattern

``def create_index(words)``  index = Hash.new{[]}``  words.each do |word|``    index[word[0]] << word``  end``  index``end``
``class Array``  def group_by``    each_with_object(Hash.new{[]}){|obj, item|``      obj[yield(item)] << item}``  end``end``def create_index(words)``  words.group_by{|word| word[0]}``end``

Demo

## Example Start

``````def words_that_appear_3_times(words)
word_counts = Hash.new{0}
words.each do |word|
word_counts[word] += 1
end
``````
three_peats = []
word_counts.each do |word, count|
three_peats << word if count == 3
end
three_peats
end

words = %w(bob bob bob bill bill jim jim jim jim jake jake jake)

words_that_appear_3_times(words)``````

## Example end

``````def words_that_appear_3_times(words)
words.group_by(&:to_s).select{|_, occurrences| occurrences.size == 3}.map{|word, _| word}
end

words = %w(bob bob bob bill bill jim jim jim jim jake jake jake)

words_that_appear_3_times(words)``````

By blatyo

• 772