Repository: abs-lang/abs
Pull request #346 (2020-03-27 12:31:01)

symmetric vs asymmetric difference
Computes the difference between 2 arrays,
returning elements that are only on the first array:

```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]) # []
```

For symmetric difference see [diff_symmetric(...)](#diff_symmetricarray)

Computes the [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_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]
```


Patch:
diff --git a/docs/types/array.md b/docs/types/array.md
index e2838b6..f68458a 100644
--- a/docs/types/array.md
+++ b/docs/types/array.md
@@ -378,8 +378,22 @@ Computes the intersection between 2 arrays:
 
 ### diff(array)
 
-Computes the difference between 2 arrays
-(elements that are only on either of the 2):
+Computes the difference between 2 arrays,
+returning elements that are only on the first array:
+
+```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]) # []
+```
+
+For symmetric difference see [diff_symmetric(...)](#diff_symmetricarray)
+
+### diff_symmetric(array)
+
+Computes the [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
+between 2 arrays (elements that are only on either of the 2):
 
 ```bash
 [1, 2, 3].diff([]) # [1, 2, 3]
diff --git a/evaluator/functions.go b/evaluator/functions.go
index 6a770d4..5516325 100644
--- a/evaluator/functions.go
+++ b/evaluator/functions.go
@@ -208,6 +208,11 @@ func getFns() map[string]*object.Builtin {
 			Types: []string{object.ARRAY_OBJ},
 			Fn:    diffFn,
 		},
+		// diff_symmetric(array:[1, 2, 3], array:[1, 2, 3])
+		"diff_symmetric": &object.Builtin{
+			Types: []string{object.ARRAY_OBJ},
+			Fn:    diffSymmetricFn,
+		},
 		// map(array:[1, 2, 3], function:f(x) { x + 1 })
 		"map": &object.Builtin{
 			Types: []string{object.ARRAY_OBJ},
@@ -1209,8 +1214,8 @@ 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 {
+// diff(array:[1, 2, 3], array:[1, 2, 3])
+func diff(symmetric bool, 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
@@ -1218,14 +1223,9 @@ func diffFn(tok token.Token, env *object.Environment, args ...object.Object) obj
 
 	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
 	}
@@ -1238,17 +1238,24 @@ func diffFn(tok token.Token, env *object.Environment, args ...object.Object) obj
 		}
 	}
 
-	for _, o := range right {
-		_, ok := foundLeft[string(o.Type())+"__"+o.Inspect()]
-
-		if !ok {
-			difference = append(difference, o)
-		}
+	if symmetric {
+		// If the did is symmetric, we simply re-run this function with the arrays swapped
+		// so diff_sym(a, b) = diff(a, b) + diff(b, a)
+		difference = append(difference, diff(false, tok, env, args[1], args[0]).(*object.Array).Elements...)
 	}
 
 	return &object.Array{Elements: difference}
 }
 
+func diffFn(tok token.Token, env *object.Environment, args ...object.Object) object.Object {
+	return diff(false, tok, env, args...)
+}
+
+// diff_symmetric(array:[1, 2, 3], array:[1, 2, 3])
+func diffSymmetricFn(tok token.Token, env *object.Environment, args ...object.Object) object.Object {
+	return diff(true, tok, env, args...)
+}
+
 // 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}})
@@ -1393,7 +1400,7 @@ func filterFn(tok token.Token, env *object.Environment, args ...object.Object) o
 
 // unique(array:[1, 2, 3])
 func uniqueFn(tok token.Token, env *object.Environment, args ...object.Object) object.Object {
-	err := validateArgs(tok, "filter", args, 1, [][]string{{object.ARRAY_OBJ}})
+	err := validateArgs(tok, "unique", args, 1, [][]string{{object.ARRAY_OBJ}})
 	if err != nil {
 		return err
 	}


Test patch:
diff --git a/evaluator/builtin_functions_test.go b/evaluator/builtin_functions_test.go
index d5dab67..f2eced9 100644
--- a/evaluator/builtin_functions_test.go
+++ b/evaluator/builtin_functions_test.go
@@ -639,7 +639,18 @@ func TestDiff(t *testing.T) {
 		{`[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}},
+		{`[1,2,3].diff([1,2,3,4])`, []int{}},
+	}
+
+	testBuiltinFunction(tests, t)
+}
+
+func TestDiffSymmetric(t *testing.T) {
+	tests := []Tests{
+		{`[1,2,3].diff_symmetric([])`, []int{1, 2, 3}},
+		{`[1,2,3].diff_symmetric([3])`, []int{1, 2}},
+		{`[1,2,3].diff_symmetric([3, 1])`, []int{2}},
+		{`[1,2,3].diff_symmetric([1,2,3,4])`, []int{4}},
 	}
 
 	testBuiltinFunction(tests, t)