diff --git a/docs/types/array.md b/docs/types/array.md index 3a060e9a..e2838b68 100644 --- a/docs/types/array.md +++ b/docs/types/array.md @@ -376,6 +376,18 @@ Computes the intersection between 2 arrays: [1, 2, 3].intersect([1, 2, 3, 4]) # [1, 2, 3] ``` +### diff(array) + +Computes the difference between 2 arrays +(elements that are only on either of the 2): + +```bash +[1, 2, 3].diff([]) # [1, 2, 3] +[1, 2, 3].diff([3]) # [1, 2] +[1, 2, 3].diff([3, 1]) # [2] +[1, 2, 3].diff([1, 2, 3, 4]) # [4] +``` + ## Next That's about it for this section! diff --git a/evaluator/builtin_functions_test.go b/evaluator/builtin_functions_test.go index f48c122e..d5dab67c 100644 --- a/evaluator/builtin_functions_test.go +++ b/evaluator/builtin_functions_test.go @@ -634,6 +634,17 @@ func TestIntersect(t *testing.T) { testBuiltinFunction(tests, t) } +func TestDiff(t *testing.T) { + tests := []Tests{ + {`[1,2,3].diff([])`, []int{1, 2, 3}}, + {`[1,2,3].diff([3])`, []int{1, 2}}, + {`[1,2,3].diff([3, 1])`, []int{2}}, + {`[1,2,3].diff([1,2,3,4])`, []int{4}}, + } + + testBuiltinFunction(tests, t) +} + func testBuiltinFunction(tests []Tests, t *testing.T) { for _, tt := range tests { evaluated := testEval(tt.input) diff --git a/evaluator/functions.go b/evaluator/functions.go index d791d896..6a770d47 100644 --- a/evaluator/functions.go +++ b/evaluator/functions.go @@ -203,6 +203,11 @@ func getFns() map[string]*object.Builtin { Types: []string{object.ARRAY_OBJ}, Fn: intersectFn, }, + // diff(array:[1, 2, 3], array:[1, 2, 3]) + "diff": &object.Builtin{ + Types: []string{object.ARRAY_OBJ}, + Fn: diffFn, + }, // map(array:[1, 2, 3], function:f(x) { x + 1 }) "map": &object.Builtin{ Types: []string{object.ARRAY_OBJ}, @@ -1204,6 +1209,46 @@ func intersectFn(tok token.Token, env *object.Environment, args ...object.Object return &object.Array{Elements: intersection} } +// intersect(array:[1, 2, 3], array:[1, 2, 3]) +func diffFn(tok token.Token, env *object.Environment, args ...object.Object) object.Object { + err := validateArgs(tok, "diff", args, 2, [][]string{{object.ARRAY_OBJ}, {object.ARRAY_OBJ}}) + if err != nil { + return err + } + + left := args[0].(*object.Array).Elements + right := args[1].(*object.Array).Elements + foundLeft := map[string]object.Object{} + foundRight := map[string]object.Object{} + difference := []object.Object{} + + for _, o := range left { + foundLeft[string(o.Type())+"__"+o.Inspect()] = o + } + + for _, o := range right { + foundRight[string(o.Type())+"__"+o.Inspect()] = o + } + + for _, o := range left { + _, ok := foundRight[string(o.Type())+"__"+o.Inspect()] + + if !ok { + difference = append(difference, o) + } + } + + for _, o := range right { + _, ok := foundLeft[string(o.Type())+"__"+o.Inspect()] + + if !ok { + difference = append(difference, o) + } + } + + return &object.Array{Elements: difference} +} + // map(array:[1, 2, 3], function:f(x) { x + 1 }) func mapFn(tok token.Token, env *object.Environment, args ...object.Object) object.Object { err := validateArgs(tok, "map", args, 2, [][]string{{object.ARRAY_OBJ}, {object.FUNCTION_OBJ, object.BUILTIN_OBJ}})