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()[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

  •    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)))
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()[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

  •    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)))
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()[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

  •    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!