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)
}
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()[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')
)
)
)
)
)
),
})
-
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:
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]]
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()[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' ] ]
-
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:
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 {}]]]
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()[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 ] ]
]
-
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, andSelect
is use to access the value and returnnull
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!