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}");
}
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());
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')
)
)
)
)
)
),
})
-
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)))
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"]]
Call('zip', ['A', 'B', 'O'], ['Apple', 'Banana', 'Orange'])
[ [ 'A', 'Apple' ], [ 'B', 'Banana' ], [ 'O', 'Orange' ] ]
-
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)))
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]]]
[
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 ] ]
]
-
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
Lambdauses the size of the accumulator as the index into the two provided arrays, andSelectis use to access the value and returnnullif no value exists at that index.