Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions source/includes/ref-toc-aggregation-array.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,9 @@ name: :expression:`$slice`
description: |
Returns a subset of an array.
file: /reference/operator/aggregation/slice
---
name: :expression:`$zip`
description: |
Merge two lists together.
file: /reference/operator/aggregation/zip
...
225 changes: 225 additions & 0 deletions source/reference/operator/aggregation/zip.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
==================
$zip (aggregation)
==================

.. default-domain:: mongodb

.. contents:: On this page
:local:
:backlinks: none
:depth: 1
:class: singlecol

Definition
----------

.. expression:: $zip

.. versionadded:: 3.4

Transposes an array of input arrays so that the first element of
the output array would be an array containing, the first element of
the first input array, the first element of the second input array,
etc.

For example, :expression:`$zip` would transform
``[ [ 1, 2, 3 ], [ "a", "b", "c" ] ]`` into
``[ [ 1, "a" ], [ 2, "b" ], [ 3, "c" ] ]``.

:expression:`$zip` has the following syntax:

.. code-block:: javascript

{
$zip: {
inputs: [ <array expression1>, ... ],
useLongestLength: <boolean>,
defaults: <array expression>
}
}

.. list-table::
:header-rows: 1
:widths: 30 65

* - Operand
- Description

* - ``inputs``

- An array of :ref:`expressions <aggregation-expressions>` that
resolve to arrays. The elements of these input arrays combine
to form the arrays of the output array.

.. include:: /includes/extracts/agg-expression-null-operand-zip.rst

* - ``useLongestLength``

- A boolean which specifies whether the length of the longest
array determines the number of arrays in the output array.

The default value is ``false``: the shortest array length
determines the number of arrays in the output array.

* - ``defaults``

- An array of default element values to use if the input arrays
have different lengths. You must specify
``useLongestLength: true`` along with this field, or else
:expression:`$zip` will return an error.

If ``useLongestLength: true`` but ``defaults`` is empty or not
specified, :expression:`$zip` uses ``null`` as the default
value.

If specifying a non-empty ``defaults``, you must specify a
default for *each* input array or else :expression:`$zip`
will return an error.

Behavior
--------

The input arrays do not need to be of the same length. By default,
the output array has the length of the shortest input array, but the
``useLongestLength`` option instructs :expression:`$zip` to output
an array as long as the longest input array.

.. list-table::
:header-rows: 1
:widths: 55 35

* - Example
- Results

* - ``{ $zip: { inputs: [ [ "a" ], [ "b" ], [ "c" ] ] }``
- ``[ [ "a", "b", "c" ] ]``

* - ``{ $zip: { inputs: [ [ "a" ], [ "b", "c" ] ] } }``
- ``[ [ "a", "b" ] ]``

* - .. code-block:: javascript

{
$zip: {
inputs: [ [ 1 ], [ 2, 3 ] ],
useLongestLength: true
}
}

- ``[ [ 1, 2 ], [ null, 3 ] ]``

* - .. code-block:: javascript

{
$zip: {
inputs: [ [ 1 ], [ 2, 3 ], [ 4 ] ],
useLongestLength: true,
defaults: [ "a", "b", "c" ]
}
}

- Because ``useLongestLength: true``, ``$zip`` will pad the shorter
input arrays with the corresponding ``defaults`` elements.

This yields ``[ [ 1, 2, 4 ], [ "a", 3, "c" ] ]``.

Example
-------

Matrix Transposition
~~~~~~~~~~~~~~~~~~~~

A collection called ``matrices`` contains the following documents:

.. code-block:: javascript

db.matrices.insertMany([
{ matrix: [[1, 2], [2, 3], [3, 4]] },
{ matrix: [[8, 7], [7, 6], [5, 4]] },
])

To compute the transpose of each 3x2 matrix in this collection, you can
use the following aggregation operation:

.. code-block:: javascript

db.matrices.aggregate([{
$project: {
_id: false,
transposed: {
$zip: {
inputs: [
{ $arrayElemAt: [ "$matrix", 0 ] },
{ $arrayElemAt: [ "$matrix", 1 ] },
{ $arrayElemAt: [ "$matrix", 2 ] },
]
}
}
}
}])

This will return the following 2x3 matrices:

.. code-block:: javascript

{ "transposed" : [ [ 1, 2, 3 ], [ 2, 3, 4 ] ] }
{ "transposed" : [ [ 8, 7, 5 ], [ 7, 6, 4 ] ] }

Filtering and Preserving Indexes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can use ``$zip`` with :expression:`$filter` to obtain a subset of
elements in an array, saving the original index alongside each
retained element.

A collection called ``pages`` contains the following document:

.. code-block:: javascript

db.pages.save( {
"category": "unix",
"pages": [
{ "title": "awk for beginners", reviews: 5 },
{ "title": "sed for newbies", reviews: 0 },
{ "title": "grep made simple", reviews: 2 },
] } )

The following aggregation pipeline will first zip the elements of the
``pages`` array together with their index, and then filter out only the
pages with at least one review:

.. code-block:: javascript

db.pages.aggregate([{
$project: {
_id: false,
pages: {
$filter: {
input: {
$zip: {
inputs: [ "$pages", { $range: [0, { $size: "$pages" }] } ]
}
},
as: "pageWithIndex",
cond: {
$let: {
vars: {
page: { $arrayElemAt: [ "$$pageWithIndex", 0 ] }
},
in: { $gte: [ "$$page.reviews", 1 ] }
}
}
}
}
}
}])

This will return the following document:

.. code-block:: javascript

{
"pages" : [
[ { "title" : "awk for beginners", "reviews" : 5 }, 0 ],
[ { "title" : "grep made simple", "reviews" : 2 }, 2 ] ]
}