FQL v4 will be decommissioned on June 30, 2025. Ensure that you complete your migration from FQL v4 to FQL v10 by that date.

For more details, see the v4 EOL announcement and migration guide. Contact support@fauna.com with any questions.

Zip together two arrays

Problem

You want to "zip" two arrays together. ["A", "B", "O"] and ["Apple", "Banana", "Orange"] should become [["A", "Apple"], ["B", "Banana"], ["O", "Orange"]].

Solution

Fauna doesn’t provide a Zip function, but we can create one:

client.query(
  q.CreateFunction({
    name: 'zip',
    body: q.Query(
      q.Lambda(
        ['arr1', 'arr2'],
        q.If(
          q.Not(
            q.And(
              q.IsArray(q.Var('arr1')),
              q.IsArray(q.Var('arr2')),
            )
          ),
          q.Abort('zip requires two array arguments'),
          q.Let(
            {
              count1: q.Count(q.Var('arr1')),
              count2: q.Count(q.Var('arr2')),
            },
            q.Reduce(
              q.Lambda(
                ['acc', 'val'],
                q.Let(
                  {
                    cnt: q.Count(q.Var('acc')),
                    a: q.Select(q.Var('cnt'), q.Var('arr1'), null),
                    b: q.Select(q.Var('cnt'), q.Var('arr2'), null),
                  },
                  q.Append([[q.Var('a'), q.Var('b')]], q.Var('acc'))
                )
              ),
              [],
              q.If(
                q.GTE(q.Var('count1'), q.Var('count2')),
                q.Var('arr1'),
                q.Var('arr2')
              )
            )
          )
        )
      )
    ),
  })
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
result = client.query(
  q.create_function({
    "name": "zip",
    "body": q.query(
      q.lambda_(
        ["arr1", "arr2"],
        q.if_(
          q.not_(
            q.and_(
              q.is_array(q.var("arr1")),
              q.is_array(q.var("arr2")),
            )
          ),
          q.abort("zip requires two array arguments"),
          q.let(
            {
              "count1": q.count(q.var("arr1")),
              "count2": q.count(q.var("arr2")),
            },
            q.reduce(
              q.lambda_(
                ["acc", "val"],
                q.let(
                  {
                    "cnt": q.count(q.var("acc")),
                    "a": q.select(q.var("cnt"), q.var("arr1"), None),
                    "b": q.select(q.var("cnt"), q.var("arr2"), None),
                  },
                  q.append([[q.var("a"), q.var("b")]], q.var("acc"))
                )
              ),
              [],
              q.if_(
                q.gte(q.var("count1"), q.var("count2")),
                q.var("arr1"),
                q.var("arr2")
              )
            )
          )
        )
      )
    ),
  })
)
print(result)
result, err := client.Query(
	f.CreateFunction(
		f.Obj{
			"name": "zip",
			"body": f.Query(
				f.Lambda(
					f.Arr{"arr1", "arr2"},
					f.If(
						f.Not(
							f.And(
								f.IsArray(f.Var("arr1")),
								f.IsArray(f.Var("arr2")),
							),
						),
						f.Abort("zip requires two array arguments"),
						f.Let().Bind(
							"count1", f.Count(f.Var("arr1"))).Bind(
							"count2", f.Count(f.Var("arr2"))).In(
							f.Reduce(
								f.Lambda(
									f.Arr{"acc", "val"},
									f.Let().Bind(
										"cnt", f.Count(
											f.Var("acc"),
										),
									).Bind(
										"a", f.Select(
											f.Var("cnt"),
											f.Var("arr1"),
											f.Default(nil),
										),
									).Bind(
										"b", f.Select(
											f.Var("cnt"),
											f.Var("arr2"),
											f.Default(nil)),
									).In(
										f.Append(
											f.Arr{
												f.Arr{
													f.Var("a"),
													f.Var("b"),
												},
											},
											f.Var("acc"),
										),
									),
								),
								f.Arr{},
								f.If(
									f.GTE(f.Var("count1"), f.Var("count2")),
									f.Var("arr1"),
									f.Var("arr2"),
								),
							),
						),
					),
				),
			),
		},
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
try
{
    Value result = await client.Query(
        CreateFunction(
            Obj(
                "name", "zip",
                "body", Query(
                    Lambda(
                        Arr("arr1", "arr2"),
                        If(
                            Not(
                                And(
                                    IsArray(Var("arr1")),
                                    IsArray(Var("arr2"))
                                )
                            ),
                            Abort("zip requires two array arguments"),
                            Let(
                                "count1", Count(Var("arr1")),
                                "count2", Count(Var("arr2"))
                            ).In(
                                Reduce(
                                    Lambda(
                                        Arr("acc", "val"),
                                        Let(
                                            "s", Count(Var("acc")),
                                            "a", Select(
                                                Var("s"),
                                                Var("arr1"),
                                                Null()
                                            ),
                                            "b", Select(
                                                Var("s"),
                                                Var("arr2"),
                                                Null()
                                            )
                                        ).In(
                                            Append(
                                                Arr(
                                                    Arr(
                                                        Var("a"),
                                                        Var("b")
                                                    )
                                                ),
                                                Var("acc")
                                            )
                                        )
                                    ),
                                    Arr(),
                                    If(
                                        GTE(
                                            Var("count1"),
                                            Var("count2")
                                        ),
                                        Var("arr1"),
                                        Var("arr2")
                                    )
                                )
                            )
                        )
                    )
                )
            )
        )
    );

    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
CreateFunction({
  name: 'zip',
  body: Query(
    Lambda(
      ['arr1', 'arr2'],
      If(
        Not(
          And(
            IsArray(Var('arr1')),
            IsArray(Var('arr2')),
          )
        ),
        Abort('zip requires two array arguments'),
        Let(
          {
            count1: Count(Var('arr1')),
            count2: Count(Var('arr2')),
          },
          Reduce(
            Lambda(
              ['acc', 'val'],
              Let(
                {
                  cnt: Count(Var('acc')),
                  a: Select(Var('cnt'), Var('arr1'), null),
                  b: Select(Var('cnt'), Var('arr2'), null),
                },
                Append([[Var('a'), Var('b')]], Var('acc'))
              )
            ),
            [],
            If(
              GTE(Var('count1'), Var('count2')),
              Var('arr1'),
              Var('arr2')
            )
          )
        )
      )
    )
  ),
})
Query metrics:
  •    bytesIn:  736

  •   bytesOut:  818

  • computeOps:    1

  •    readOps:    0

  •   writeOps:    1

  •  readBytes:   16

  • writeBytes:  778

  •  queryTime: 10ms

  •    retries:    0

The following query calls the UDF with two arrays:

client.query(
  q.Call('zip', ['A', 'B', 'O'], ['Apple', 'Banana', 'Orange'])
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
[ [ 'A', 'Apple' ], [ 'B', 'Banana' ], [ 'O', 'Orange' ] ]
result = client.query(
  q.call('zip', ['A', 'B', 'C'], ['Apple', 'Banana', 'Orange'])
)
print(result)
[['A', 'Apple'], ['B', 'Banana'], ['C', 'Orange']]
result, err := client.Query(
	f.Call(
		"zip",
		f.Arr{"A", "B", "O"},
		f.Arr{"Apple", "Banana", "Orange"},
	))

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
[[A Apple] [B Banana] [O Orange]]
try
{
    Value result = await client.Query(
        Call(
            "zip",
            Arr("A", "B", "O"),
            Arr("Apple", "Banana", "Orange")
        )
    );

    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
Arr(Arr(StringV(A), StringV(Apple)), Arr(StringV(B), StringV(Banana)), Arr(StringV(O), StringV(Orange)))
Call('zip', ['A', 'B', 'O'], ['Apple', 'Banana', 'Orange'])
[ [ 'A', 'Apple' ], [ 'B', 'Banana' ], [ 'O', 'Orange' ] ]
Query metrics:
  •    bytesIn:  70

  •   bytesOut:  58

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes:   0

  • writeBytes:   0

  •  queryTime: 1ms

  •    retries:   0

The UDF handles the situation where either array is longer than the other:

client.query([
  q.Call('zip', ['A', 'B'], ['Apple', 'Banana', 'Orange']),
  q.Call('zip', ['A', 'B', 'O'], ['Apple', 'Banana']),
])
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
[
  [ [ 'A', 'Apple' ], [ 'B', 'Banana' ], [ null, 'Orange' ] ],
  [ [ 'A', 'Apple' ], [ 'B', 'Banana' ], [ 'O', null ] ]
]
result = client.query([
  q.call('zip', ['A', 'B'], ['Apple', 'Banana', 'Orange']),
  q.call('zip', ['A', 'B', 'O'], ['Apple', 'Banana']),
])
print(result)
[[['A', 'Apple'], ['B', 'Banana'], [None, 'Orange']], [['A', 'Apple'], ['B', 'Banana'], ['O', None]]]
result, err := client.Query(
	f.Arr{
		f.Call(
			"zip",
			f.Arr{"A", "B"},
			f.Arr{"Apple", "Banana", "Orange"},
		),
		f.Call(
			"zip",
			f.Arr{"A", "B", "O"},
			f.Arr{"Apple", "Banana"},
		),
	})

if err != nil {
	fmt.Fprintln(os.Stderr, err)
} else {
	fmt.Println(result)
}
[[[A Apple] [B Banana] [{} Orange]] [[A Apple] [B Banana] [O {}]]]
try
{
    Value result = await client.Query(
        Arr(
            Call(
                "zip",
                Arr("A", "B"),
                Arr("Apple", "Banana", "Orange")
            ),
            Call(
                "zip",
                Arr("A", "B", "O"),
                Arr("Apple", "Banana")
            )
        )
    );

    Console.WriteLine(result);
}
catch (Exception e)
{
    Console.WriteLine($"ERROR: {e.Message}");
}
Arr(Arr(Arr(StringV(A), StringV(Apple)), Arr(StringV(B), StringV(Banana)), Arr(NullV, StringV(Orange))), Arr(Arr(StringV(A), StringV(Apple)), Arr(StringV(B), StringV(Banana)), Arr(StringV(O), NullV)))
[
  Call('zip', ['A', 'B'], ['Apple', 'Banana', 'Orange']),
  Call('zip', ['A', 'B', 'O'], ['Apple', 'Banana']),
]
[
  [ [ 'A', 'Apple' ], [ 'B', 'Banana' ], [ null, 'Orange' ] ],
  [ [ 'A', 'Apple' ], [ 'B', 'Banana' ], [ 'O', null ] ]
]
Query metrics:
  •    bytesIn: 130

  •   bytesOut: 103

  • computeOps:   1

  •    readOps:   0

  •   writeOps:   0

  •  readBytes:   0

  • writeBytes:   0

  •  queryTime: 1ms

  •    retries:   0

Discussion

The main logic is within the Reduce function call:

  • An empty array is provided as the initial accumulator.

  • The longest array is used as the list to iterate.

  • On each invocation, the reducer’s Lambda uses the size of the accumulator as the index into the two provided arrays, and Select is use to access the value and return null if no value exists at that index.

Is this article helpful? 

Tell Fauna how the article can be improved:
Visit Fauna's forums or email docs@fauna.com

Thank you for your feedback!