Learn the "why" behind slow queries and how to fix them in our 2-Part Webinar.
Register now >
Docs Menu
Docs Home
/ /

$reduce (expression operator)

$reduce

Applies an expression to each element in an array and combines them into a single value.

$reduce has the following syntax:

{
$reduce: {
input: <array>,
initialValue: <expression>,
in: <expression>,
as: <string>,
valueAs: <string>,
arrayIndexAs: <string>
}
}
Field
Type
Description

input

array

Can be any valid expression that resolves to an array. For more information on expressions, see Expressions.

If the argument resolves to a value of null or refers to a missing field, $reduce returns null.

If the argument does not resolve to an array or null nor refers to a missing field, $reduce returns an error.

initialValue

expression

The initial cumulative value set before in is applied to the first element of the input array.

in

expression

A valid expression that $reduce applies to each element in the input array in left-to-right order. Wrap the input value with $reverseArray to yield the equivalent of applying the combining expression from right-to-left.

During evaluation of the in expression, two variables will be available:

  • value is the variable that represents the cumulative value of the expression.

  • this is the variable that refers to the element being processed.

as

string

Optional. A name for the variable that represents each individual element of the input array. If omitted, the variable name defaults to this.

New in version 8.3.

valueAs

string

Optional. A name for the variable that represents the cumulative value of the expression. If omitted, the variable name defaults to value.

New in version 8.3.

arrayIndexAs

string

Optional. A name for the aggregation variable that represents the index of the current element in the input array. The first array element index is 0.

You can use the variable name in an expression. For example, if you specify arrayIndexAs: "myIndex", you use $$myIndex in the expression. $$myIndex returns the index of the current element in the input array.

If you omit arrayIndexAs, you can use the $$IDX system variable in the expression to return the index of the current element.

For examples, see Access the Index of Each Item in an Array and Use $$IDX to Access the Index.

New in version 8.3.

If input resolves to an empty array, $reduce returns initialValue.

Example
Results
{
$reduce: {
input: ["a", "b", "c"],
initialValue: "",
in: { $concat : ["$$value", "$$this"] }
}
}

"abc"

{
$reduce: {
input: [ 1, 2, 3, 4 ],
initialValue: { sum: 5, product: 2 },
in: {
sum: { $add : ["$$value.sum", "$$this"] },
product: { $multiply: [ "$$value.product", "$$this" ] }
}
}
}

{ "sum" : 15, "product" : 48 }

{
$reduce: {
input: [ [ 3, 4 ], [ 5, 6 ] ],
initialValue: [ 1, 2 ],
in: { $concatArrays : ["$$value", "$$this"] }
}
}

[ 1, 2, 3, 4, 5, 6 ]

A collection named events contains the events of a probability experiment. Each experiment can have multiple events, such as rolling a die several times or drawing several cards (without replacement) in succession to achieve a desired result. In order to obtain the overall probability of the experiment, we will need to multiply the probability of each event in the experiment.

db.events.insertMany( [
{ _id : 1, type : "die", experimentId :"r5", description : "Roll a 5", eventNum : 1, probability : 0.16666666666667 },
{ _id : 2, type : "card", experimentId :"d3rc", description : "Draw 3 red cards", eventNum : 1, probability : 0.5 },
{ _id : 3, type : "card", experimentId :"d3rc", description : "Draw 3 red cards", eventNum : 2, probability : 0.49019607843137 },
{ _id : 4, type : "card", experimentId :"d3rc", description : "Draw 3 red cards", eventNum : 3, probability : 0.48 },
{ _id : 5, type : "die", experimentId :"r16", description : "Roll a 1 then a 6", eventNum : 1, probability : 0.16666666666667 },
{ _id : 6, type : "die", experimentId :"r16", description : "Roll a 1 then a 6", eventNum : 2, probability : 0.16666666666667 },
{ _id : 7, type : "card", experimentId :"dak", description : "Draw an ace, then a king", eventNum : 1, probability : 0.07692307692308 },
{ _id : 8, type : "card", experimentId :"dak", description : "Draw an ace, then a king", eventNum : 2, probability : 0.07843137254902 }
] )

Steps:

  1. Use $group to group by the experimentId and use $push to create an array with the probability of each event.

  2. Use $reduce with $multiply to multiply and combine the elements of probabilityArr into a single value and project it.

db.probability.aggregate(
[
{
$group: {
_id: "$experimentId",
probabilityArr: { $push: "$probability" }
}
},
{
$project: {
description: 1,
results: {
$reduce: {
input: "$probabilityArr",
initialValue: 1,
in: { $multiply: [ "$$value", "$$this" ] }
}
}
}
}
]
)

The operation returns the following:

{ _id : "dak", results : 0.00603318250377101 }
{ _id : "r5", results : 0.16666666666667 }
{ _id : "r16", results : 0.027777777777778886 }
{ _id : "d3rc", results : 0.11764705882352879 }

A collection named clothes contains the following documents:

db.clothes.insertMany( [
{ _id : 1, productId : "ts1", description : "T-Shirt", color : "black", size : "M", price : 20, discounts : [ 0.5, 0.1 ] },
{ _id : 2, productId : "j1", description : "Jeans", color : "blue", size : "36", price : 40, discounts : [ 0.25, 0.15, 0.05 ] },
{ _id : 3, productId : "s1", description : "Shorts", color : "beige", size : "32", price : 30, discounts : [ 0.15, 0.05 ] },
{ _id : 4, productId : "ts2", description : "Cool T-Shirt", color : "White", size : "L", price : 25, discounts : [ 0.3 ] },
{ _id : 5, productId : "j2", description : "Designer Jeans", color : "blue", size : "30", price : 80, discounts : [ 0.1, 0.25 ] }
] )

Each document contains a discounts array containing the currently available percent-off coupons for each item. If each discount can be applied to the product once, we can calculate the lowest price by using $reduce to apply the following formula for each element in the discounts array: (1 - discount) * price.

db.clothes.aggregate(
[
{
$project: {
discountedPrice: {
$reduce: {
input: "$discounts",
initialValue: "$price",
in: { $multiply: [ "$$value", { $subtract: [ 1, "$$this" ] } ] }
}
}
}
}
]
)

The operation returns the following:

{ _id : ObjectId("57c893067054e6e47674ce01"), discountedPrice : 9 }
{ _id : ObjectId("57c9932b7054e6e47674ce12"), discountedPrice : 24.224999999999998 }
{ _id : ObjectId("57c993457054e6e47674ce13"), discountedPrice : 24.224999999999998 }
{ _id : ObjectId("57c993687054e6e47674ce14"), discountedPrice : 17.5 }
{ _id : ObjectId("57c993837054e6e47674ce15"), discountedPrice : 54 }

A collection named people contains the following documents:

db.people.insertMany( [
{ _id : 1, name : "Melissa", hobbies : [ "softball", "drawing", "reading" ] },
{ _id : 2, name : "Brad", hobbies : [ "gaming", "skateboarding" ] },
{ _id : 3, name : "Scott", hobbies : [ "basketball", "music", "fishing" ] },
{ _id : 4, name : "Tracey", hobbies : [ "acting", "yoga" ] },
{ _id : 5, name : "Josh", hobbies : [ "programming" ] },
{ _id : 6, name : "Claire" }
] )

The following example reduces the hobbies array of strings into a single string bio:

db.people.aggregate(
[
// Filter to return only non-empty arrays
{ $match: { "hobbies": { $gt: [ ] } } },
{
$project: {
name: 1,
bio: {
$reduce: {
input: "$hobbies",
initialValue: "My hobbies include:",
in: {
$concat: [
"$$value",
{
$cond: {
if: { $eq: [ "$$value", "My hobbies include:" ] },
then: " ",
else: ", "
}
},
"$$this"
]
}
}
}
}
}
]
)

The operation returns the following:

{ _id : 1, name : "Melissa", bio : "My hobbies include: softball, drawing, reading" }
{ _id : 2, name : "Brad", bio : "My hobbies include: gaming, skateboarding" }
{ _id : 3, name : "Scott", bio : "My hobbies include: basketball, music, fishing" }
{ _id : 4, name : "Tracey", bio : "My hobbies include: acting, yoga" }
{ _id : 5, name : "Josh", bio : "My hobbies include: programming" }

A collection named matrices contains the following documents:

db.matrices.insertMany( [
{ _id : 1, arr : [ [ 24, 55, 79 ], [ 14, 78, 35 ], [ 84, 90, 3 ], [ 50, 89, 70 ] ] },
{ _id : 2, arr : [ [ 39, 32, 43, 7 ], [ 62, 17, 80, 64 ], [ 17, 88, 11, 73 ] ] },
{ _id : 3, arr : [ [ 42 ], [ 26, 59 ], [ 17 ], [ 72, 19, 35 ] ] },
{ _id : 4 }
] )

The following example collapses the two dimensional arrays into a single array collapsed:

db.arrayconcat.aggregate(
[
{
$project: {
collapsed: {
$reduce: {
input: "$arr",
initialValue: [ ],
in: { $concatArrays: [ "$$value", "$$this" ] }
}
}
}
}
]
)

The operation returns the following:

{ _id : 1, collapsed : [ 24, 55, 79, 14, 78, 35, 84, 90, 3, 50, 89, 70 ] }
{ _id : 2, collapsed : [ 39, 32, 43, 7, 62, 17, 80, 64, 17, 88, 11, 73 ] }
{ _id : 3, collapsed : [ 42, 26, 59, 17, 72, 19, 35 ] }
{ _id : 4, collapsed : null }

The following example performs the same two dimensional array collapse as the example above, but also creates a new array containing only the first element of each array.

db.arrayconcat.aggregate(
[
{
$project: {
results: {
$reduce: {
input: "$arr",
initialValue: [ ],
in: {
collapsed: {
$concatArrays: [ "$$value.collapsed", "$$this" ]
},
firstValues: {
$concatArrays: [ "$$value.firstValues", { $slice: [ "$$this", 1 ] } ]
}
}
}
}
}
}
]
)

The operation returns the following:

{ _id : 1, results : { collapsed : [ 24, 55, 79, 14, 78, 35, 84, 90, 3, 50, 89, 70 ], firstValues : [ 24, 14, 84, 50 ] } }
{ _id : 2, results : { collapsed : [ 39, 32, 43, 7, 62, 17, 80, 64, 17, 88, 11, 73 ], firstValues : [ 39, 62, 17 ] } }
{ _id : 3, results : { collapsed : [ 42, 26, 59, 17, 72, 19, 35 ], firstValues : [ 42, 26, 17, 72 ] } }
{ _id : 4, results : null }

Create a sample collection named people with these documents:

db.people.insertMany( [
{ _id: 1, name: "Melissa", hobbies: [ "softball", "drawing", "reading" ] },
{ _id: 2, name: "Brad", hobbies: [ "gaming", "skateboarding" ] },
{ _id: 3, name: "Scott", hobbies: [ "basketball", "music", "fishing" ] },
{ _id: 4, name: "Tracey", hobbies: [ "acting", "yoga" ] },
{ _id: 5, name: "Josh", hobbies: [ "programming" ] },
{ _id: 6, name: "Claire" }
] )

The hobbies field contains an array of each person's hobbies in ranked order. The first hobby in the array is the person's primary hobby that the person spends the most time on. The first hobby has an array index of 0.

The following example uses valueAs, as, and arrayIndexAs. The myIndex variable has the index of each hobby in the hobbies array. The example returns documents with these fields:

  • Person name.

  • text field, which includes the hobby name and the position of each hobby in the list.

db.people.aggregate( [
{
$project: {
_id: 0,
name: 1,
text: {
$reduce: {
input: "$hobbies",
initialValue: "My hobbies include:",
valueAs: "accumulatedText",
as: "hobby",
arrayIndexAs: "myIndex",
in: {
$concat: [
"$$accumulatedText",
" ",
{ $toString: { $add: [ "$$myIndex", 1 ] } },
") ",
"$$hobby"
]
}
}
}
}
}
] )

Output:

[
{ "name" : "Melissa", "text" : "My hobbies include: 1) softball 2) drawing 3) reading" }
{ "name" : "Brad", "text" : "My hobbies include: 1) gaming 2) skateboarding" }
{ "name" : "Scott", "text" : "My hobbies include: 1) basketball 2) music 3) fishing" }
{ "name" : "Tracey", "text" : "My hobbies include: 1) acting 2) yoga" }
{ "name" : "Josh", "text" : "My hobbies include: 1) programming" }
{ "name" : "Claire", "text" : null }
]

The $$IDX variable returns the index of the current element in the input array. You can use $$IDX if you omit the arrayIndexAs field from the expression.

The following example returns the same documents as the example in the previous section Access the Index of Each Item in an Array, but uses $$IDX instead of arrayIndexAs:

db.people.aggregate( [
{
$project: {
_id: 0,
name: 1,
text: {
$reduce: {
input: "$hobbies",
initialValue: "My hobbies include:",
valueAs: "accumulatedText",
as: "hobby",
in: {
$concat: [
"$$accumulatedText",
" ",
{ $toString: { $add: [ "$$IDX", 1 ] } },
") ",
"$$hobby"
]
}
}
}
}
}
] )

Back

$rank

On this page