Skip to content

Commit

Permalink
Add distinct() function to lists extension.
Browse files Browse the repository at this point in the history
  • Loading branch information
seirl committed Oct 14, 2024
1 parent 3214b32 commit 4d58cb2
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 0 deletions.
47 changes: 47 additions & 0 deletions ext/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ var comparableTypes = []*cel.Type{
// Lists returns a cel.EnvOption to configure extended functions for list manipulation.
// As a general note, all indices are zero-based.
//
// # Distinct
//
// Introduced in version: 2
//
// Returns the distinct elements of a list. If the element type is not comparable
// or the element types are not the same, the function will produce an error.
//
// <list(T)>.distinct() -> <list(T)>
// T in {int, uint, double, bool, duration, timestamp, string, bytes}
//
// Examples:
//
// [1, 2, 2, 3, 3, 3].distinct() // return [1, 2, 3]
// ["b", "b", "c", "a", "c"].distinct() // return ["b", "c", "a"]
// [1, "b", 2, "b"].distinct() // return [1, "b", 2]
//
// # Range
//
// Introduced in version: 2
Expand Down Expand Up @@ -269,6 +285,19 @@ func (lib listsLib) CompileOptions() []cel.EnvOption {
}),
),
))
opts = append(opts, cel.Function("distinct",
cel.MemberOverload("list_distinct",
[]*cel.Type{listType}, listType,
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
list := args[0].(traits.Lister)
result, err := distinctList(list)
if err != nil {
return types.WrapErr(err)
}
return result
}),
),
))
}

return opts
Expand Down Expand Up @@ -371,6 +400,24 @@ func sortList(list traits.Lister) (ref.Val, error) {
return types.DefaultTypeAdapter.NativeToValue(sorted), nil
}

func distinctList(list traits.Lister) (ref.Val, error) {
listLength := list.Size().(types.Int)
if listLength == 0 {
return list, nil
}
uniqueList := make([]ref.Val, 0, listLength)
seen := make(map[ref.Val]bool, listLength)
for i := types.IntZero; i < listLength; i++ {
val := list.Get(i)
if !seen[val] {
uniqueList = append(uniqueList, val)
seen[val] = true
}
}

return types.DefaultTypeAdapter.NativeToValue(uniqueList), nil
}

func templatedOverloads(types []*cel.Type, template func(t *cel.Type) cel.FunctionOpt) []cel.FunctionOpt {
overloads := make([]cel.FunctionOpt, len(types))
for i, t := range types {
Expand Down
7 changes: 7 additions & 0 deletions ext/lists_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,16 @@ func TestLists(t *testing.T) {
{expr: `[1,2,[],[],[3,4]].flatten() == [1,2,3,4]`},
{expr: `[1,[2,[3,4]]].flatten(2) == [1,2,3,4]`},
{expr: `[1,[2,[3,[4]]]].flatten(-1) == [1,2,3,4]`, err: "level must be non-negative"},
{expr: `[].sort() == []`},
{expr: `[1].sort() == [1]`},
{expr: `[4, 3, 2, 1].sort() == [1, 2, 3, 4]`},
{expr: `["d", "a", "b", "c"].sort() == ["a", "b", "c", "d"]`},
{expr: `["d", 3, 2, "c"].sort() == ["a", "b", "c", "d"]`, err: "list elements must have the same type"},
{expr: `[].distinct() == []`},
{expr: `[1].distinct() == [1]`},
{expr: `[-2, 5, -2, 1, 1, 5, -2, 1].distinct() == [-2, 5, 1]`},
{expr: `['c', 'a', 'a', 'b', 'a', 'b', 'c', 'c'].distinct() == ['c', 'a', 'b']`},
{expr: `[1, 2.0, "c", 3, "c", 1].distinct() == [1, 2.0, "c", 3]`},
}

env := testListsEnv(t)
Expand Down

0 comments on commit 4d58cb2

Please sign in to comment.