# 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:

``````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}");
}``````
``The Go version of this example is not currently available.``
``````System.out.println(
client.query(
CreateFunction(Obj(
"name", Value("zip"),
"body", Query(
Lambda(
Arr(Value("arr1"), Value("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(Value("acc"), Value("val")),
Let(
"cnt", Count(Var("acc")),
"a", Select(
Var("cnt"),
Var("arr1"),
null
),
"b", Select(
Var("cnt"),
Var("arr2"),
null
)
).in(
Append(
Arr(Arr(Var("a"), Var("b"))),
Var("acc")
)
)
),
Arr(),
If(
GTE(Var("count1"), Var("count2")),
Var("arr1"),
Var("arr2")
)
)
)
)
)
)
))
).get());``````
``````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().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)``````
``````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

•   writeOps:    1

• writeBytes:  778

•  queryTime: 10ms

•    retries:    0

The following query calls the UDF with two arrays:

``````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)))``
``The Go version of this example is not currently available.``
``````System.out.println(
client.query(
Call(
Function("zip"),
Arr(Value("A"), Value("B"), Value("O")),
Arr(Value("Apple"), Value("Banana"), Value("Orange"))
)
).get());``````
``[["A", "Apple"], ["B", "Banana"], ["O", "Orange"]]``
``````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().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']]``
``Call('zip', ['A', 'B', 'O'], ['Apple', 'Banana', 'Orange'])``
``[ [ 'A', 'Apple' ], [ 'B', 'Banana' ], [ 'O', 'Orange' ] ]``
Query metrics:
•    bytesIn:  70

•   bytesOut:  58

• computeOps:   1

•   writeOps:   0

• writeBytes:   0

•  queryTime: 1ms

•    retries:   0

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

``````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)))``
``The Go version of this example is not currently available.``
``````System.out.println(
client.query(
Arr(
Call(
Function("zip"),
Arr(Value("A"), Value("B")),
Arr(Value("Apple"), Value("Banana"), Value("Orange"))
),
Call(
Function("zip"),
Arr(Value("A"), Value("B"), Value("O")),
Arr(Value("Apple"), Value("Banana"))
)
)
).get());``````
``[[["A", "Apple"], ["B", "Banana"], [null, "Orange"]], [["A", "Apple"], ["B", "Banana"], ["O", null]]]``
``````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().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]]]``
``````[
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

•   writeOps:   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.