# How to approach a reduce problem

Joël Quenneville

Ruby’s `reduce` (aka `inject`) can be intimidating. It can be hard to both read and to write. This handy two-step approach has helped me write `reduce` code without tying my brain in knots.

## Two-step process

Here are the two steps:

1. Figure out how to combine 2 items
2. Use `reduce` to scale up to n items

They derive from a helpful mental model I have:

`reduce` is a tool for scaling a method that combines 2 items into a method that combines n items.

## Problem: Aggregating T-shirt inventory

Consider some code that models multiple warehouses that hold inventory of various sized t-shirts. We might want to find the total inventory across all warehouses. Aggregation problems commonly call for `#reduce` but this one could get rather gnarly.

``````Warehouse = Struct.new(:name, :stock)

warehouses = [
Warehouse.new("East", {small: 1}),
Warehouse.new("West", {small: 1, medium: 2, large: 1})
]
``````

## Step 1 - figure out how to combine two values

The first step is to make the problem smaller. Don’t try to figure out how to aggregate a whole list of inventories. Instead let’s start by writing a method to sum two inventories.

Because inventories are hashes, we can use `Hash#merge` to combine them. We use the block form to decide what to do when both hashes contain the same key (e.g. both have an entry for `:small`). Here we want to add them.

``````def add_inventories(inventory1, inventory2)
inventory1.merge(inventory2) do |key, val1, val2|
val1 + val2
end
end
``````

## Step 2 - use `reduce` to scale operation to n items

Now that we have a way to add two inventories, we can use `#reduce` to apply our `add_inventories` method to a whole array. Job done!

``````warehouses.reduce({}) do |running_total, warehouse|
end
``````

## Bonus points: extract value object

For extra niceness we could replace our hash of t-shirt sizes with a proper `Inventory` object. Now we have a place for that `add_inventories` object to live (that suffix was probably a big hint!) and we can even name it `+`.

``````Inventory = Data.define(:small, :medium, :large) do
def self.empty
new(small: 0, medium: 0, large: 0)
end

def +(other)
Inventory.new(
small: self.small + other.small,
medium: self.medium + other.medium,
large: self.large + other.large
)
end
end
``````

And now the scary block entirely disappears (see this article for how the non-block form works)!

``````warehouses.map(&:stock).reduce(:+)
``````

## Similar patterns: Monoids and Semigroups

This way of thinking is similar to two concepts from abstract algebra: semigroups and monoids. The core idea (slightly oversimplified) is that you have an object that:

Defines a combining method (e.g. `Inventory#+`) that combines two instances. This method must be order-independent (e.g. `a + (b + c) == (a + b) + c`). Notice that this is basically my step 1 above!

Defines an “empty” or “neutral” value (e.g. `Inventory.empty`) that does nothing when combined with another instance using the combining method defined above. For example adding an empty inventory to an existing one is the same as doing nothing `some_inventory + Inventory.empty == some_inventory`.

It turns out that arrays of these kinds of objects are inherently reduceable. You plug in the empty value as the base case and pass a symbol for the combining method and it all just works!

``````array_of_monoids.reduce(YourMonoidalObject.empty, :combine)
``````