# 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}");
}``````
``````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)
}``````
``````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)``````
``````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)))``
``````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]]``
``````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']]``
``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)))``
``````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 {}]]]``
``````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]]]``
``````[
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.