diff --git a/docs/array-functions.md b/docs/array-functions.md index c6157388..cf88ec0a 100644 --- a/docs/array-functions.md +++ b/docs/array-functions.md @@ -83,3 +83,40 @@ This function accepts a variable number of arguments. The length of the returne __Examples__ - `$zip([1,2,3], [4,5,6])` => `[[1,4] ,[2,5], [3,6]]` - `$zip([1,2,3],[4,5],[7,8,9])` => `[[1,4,7], [2,5,8]]` + +## `$union()` + +__Signature:__ `$union(array1, array2)` + +Returns a new array containing all distinct elements from both `array1` and `array2`. + +__Examples__ +- `$union([1,2,3], [3,4,5])` => `[1, 2, 3, 4, 5]` +- `$union([1,2,3], 3)` => `[1, 2, 3]` + +## `$intersection()` + +__Signature:__ `$intersection(array1, array2)` + +Returns a new array containing all distinct elements that are present in both `array1` and `array2`. + +__Examples__ +- `$intersection([1,2,3], [3,4,5])` => `[3]` + +## `$difference()` + +__Signature:__ `$difference(array1, array2)` + +Returns a new array containing all distinct elements from `array1` that are NOT present in `array2`. + +__Examples__ +- `$difference([1,2,3], [3,4,5])` => `[1, 2]` + +## `$symmetricDifference()` + +__Signature:__ `$symmetricDifference(array1, array2)` + +Returns a new array containing all distinct elements that are present in either `array1` or `array2`, but NOT in both. + +__Examples__ +- `$symmetricDifference([1,2,3], [3,4,5])` => `[1, 2, 4, 5]` diff --git a/src/functions.js b/src/functions.js index da0479e0..661e5407 100644 --- a/src/functions.js +++ b/src/functions.js @@ -2047,6 +2047,100 @@ const functions = (() => { return results; } + /** + * Helper function to check if an array includes a value using deep equality + * @param {Array} arr - the array to check + * @param {*} value - the value to look for + * @returns {boolean} - true if the value is found, false otherwise + */ + function arrayIncludes(arr, value) { + for (var ii = 0; ii < arr.length; ii++) { + if (deepEquals(value, arr[ii])) { + return true; + } + } + return false; + } + + /** + * Set Union + * @param {Array} arg1 - An array or sequence of values + * @param {Array} arg2 - An array or sequence of values + * @returns {Array} - sequence of distinct values from both arrays + */ + function union(arg1, arg2) { + if (typeof arg1 === 'undefined') return distinct.call(this, arg2); + if (typeof arg2 === 'undefined') return distinct.call(this, arg1); + var results = (isSequence(arg1) || isSequence(arg2)) ? this.createSequence() : []; + var appendArgs = arg1.concat(arg2); + for (var ii = 0; ii < appendArgs.length; ii++) { + var value = appendArgs[ii]; + if (!arrayIncludes(results, value)) { + results.push(value); + } + } + return results; + } + + /** + * Set Intersection + * @param {Array} arg1 - An array or sequence of values + * @param {Array} arg2 - An array or sequence of values + * @returns {Array} - sequence of distinct values present in both arrays + */ + function intersection(arg1, arg2) { + if (typeof arg1 === 'undefined' || typeof arg2 === 'undefined') return undefined; + var results = (isSequence(arg1) || isSequence(arg2)) ? this.createSequence() : []; + for (var ii = 0; ii < arg1.length; ii++) { + var value = arg1[ii]; + if (arrayIncludes(arg2, value) && !arrayIncludes(results, value)) { + results.push(value); + } + } + return results; + } + + /** + * Set Difference + * @param {Array} arg1 - An array or sequence of values + * @param {Array} arg2 - An array or sequence of values + * @returns {Array} - sequence of distinct values from arg1 not present in arg2 + */ + function difference(arg1, arg2) { + if (typeof arg1 === 'undefined') return undefined; + if (typeof arg2 === 'undefined') return distinct.call(this, arg1); + var results = (isSequence(arg1) || isSequence(arg2)) ? this.createSequence() : []; + for (var ii = 0; ii < arg1.length; ii++) { + var value = arg1[ii]; + if (!arrayIncludes(arg2, value) && !arrayIncludes(results, value)) { + results.push(value); + } + } + return results; + } + + /** + * Set Symmetric Difference + * @param {Array} arg1 - An array or sequence of values + * @param {Array} arg2 - An array or sequence of values + * @returns {Array} - sequence of distinct values present in either arg1 or arg2, but not both + */ + function symmetricDifference(arg1, arg2) { + if (typeof arg1 === 'undefined') return distinct.call(this, arg2); + if (typeof arg2 === 'undefined') return distinct.call(this, arg1); + var results = (isSequence(arg1) || isSequence(arg2)) ? this.createSequence() : []; + var appendArgs = arg1.concat(arg2); + for (var ii = 0; ii < appendArgs.length; ii++) { + var value = appendArgs[ii]; + var inArg1 = arrayIncludes(arg1, value); + var inArg2 = arrayIncludes(arg2, value); + if (inArg1 !== inArg2 && !arrayIncludes(results, value)) { + results.push(value); + } + } + return results; + } + /** * Applies a predicate function to each key/value pair in an object, and returns an object containing * only the key/value pairs that passed the predicate @@ -2083,7 +2177,7 @@ const functions = (() => { formatNumber, formatBase, number, floor, ceil, round, abs, sqrt, power, random, boolean, not, map, zip, filter, single, foldLeft, sift, - keys, lookup, append, exists, spread, merge, reverse, each, error, assert, type, sort, shuffle, distinct, + keys, lookup, append, exists, spread, merge, reverse, each, error, assert, type, sort, shuffle, distinct, union, intersection, difference, symmetricDifference, base64encode, base64decode, encodeUrlComponent, encodeUrl, decodeUrlComponent, decodeUrl }; })(); diff --git a/src/jsonata.js b/src/jsonata.js index 184de406..8f77bf85 100644 --- a/src/jsonata.js +++ b/src/jsonata.js @@ -1959,6 +1959,10 @@ var jsonata = (function() { staticFrame.bind('sort', defineFunction(fn.sort, '')); staticFrame.bind('shuffle', defineFunction(fn.shuffle, '')); staticFrame.bind('distinct', defineFunction(fn.distinct, '')); + staticFrame.bind('union', defineFunction(fn.union, '')); + staticFrame.bind('intersection', defineFunction(fn.intersection, '')); + staticFrame.bind('difference', defineFunction(fn.difference, '')); + staticFrame.bind('symmetricDifference', defineFunction(fn.symmetricDifference, '')); staticFrame.bind('base64encode', defineFunction(fn.base64encode, '')); staticFrame.bind('base64decode', defineFunction(fn.base64decode, '')); staticFrame.bind('encodeUrlComponent', defineFunction(fn.encodeUrlComponent, '')); diff --git a/test/test-suite/groups/function-difference/difference.json b/test/test-suite/groups/function-difference/difference.json new file mode 100644 index 00000000..f02dd86f --- /dev/null +++ b/test/test-suite/groups/function-difference/difference.json @@ -0,0 +1,42 @@ +[ + { + "expr": "$difference([1,2,3], [3,4,5])", + "data": null, + "bindings": {}, + "result": [1, 2] + }, + { + "expr": "$difference([1,2,3], 3)", + "data": null, + "bindings": {}, + "result": [1, 2] + }, + { + "expr": "$difference([1,2,3], nothing)", + "data": null, + "bindings": {}, + "result": [1, 2, 3] + }, + { + "expr": "$difference(nothing, [1,2,3])", + "data": null, + "bindings": {}, + "undefinedResult": true + }, + { + "expr": "$difference([{\"a\": 1, \"b\": 2}, 3], [{\"a\": 1, \"b\": 2}, 4])", + "data": null, + "bindings": {}, + "result": [3] + }, + { + "expr": "$difference(Account.Order.Product.SKU, [\"0406654608\", \"12345\"])", + "dataset": "dataset5", + "bindings": {}, + "result": [ + "0406634348", + "040657863", + "0406654603" + ] + } +] diff --git a/test/test-suite/groups/function-intersection/intersection.json b/test/test-suite/groups/function-intersection/intersection.json new file mode 100644 index 00000000..eb9510fb --- /dev/null +++ b/test/test-suite/groups/function-intersection/intersection.json @@ -0,0 +1,38 @@ +[ + { + "expr": "$intersection([1,2,3], [3,4,5])", + "data": null, + "bindings": {}, + "result": [3] + }, + { + "expr": "$intersection([1,2,3], 3)", + "data": null, + "bindings": {}, + "result": [3] + }, + { + "expr": "$intersection([1,2,3], nothing)", + "data": null, + "bindings": {}, + "undefinedResult": true + }, + { + "expr": "$intersection(nothing, [1,2,3])", + "data": null, + "bindings": {}, + "undefinedResult": true + }, + { + "expr": "$intersection(Account.Order.Product.SKU, [\"0406654608\", \"12345\"])", + "dataset": "dataset5", + "bindings": {}, + "result": "0406654608" + }, + { + "expr": "$intersection([{\"a\": 1, \"b\": 2}, 3], [{\"a\": 1, \"b\": 2}, 4])", + "data": null, + "bindings": {}, + "result": [{"a": 1, "b": 2}] + } +] diff --git a/test/test-suite/groups/function-symmetricDifference/symmetricDifference.json b/test/test-suite/groups/function-symmetricDifference/symmetricDifference.json new file mode 100644 index 00000000..75e8709c --- /dev/null +++ b/test/test-suite/groups/function-symmetricDifference/symmetricDifference.json @@ -0,0 +1,43 @@ +[ + { + "expr": "$symmetricDifference([1,2,3], [3,4,5])", + "data": null, + "bindings": {}, + "result": [1, 2, 4, 5] + }, + { + "expr": "$symmetricDifference([1,2,3], 3)", + "data": null, + "bindings": {}, + "result": [1, 2] + }, + { + "expr": "$symmetricDifference([1,2,3], nothing)", + "data": null, + "bindings": {}, + "result": [1, 2, 3] + }, + { + "expr": "$symmetricDifference(nothing, [1,2,3])", + "data": null, + "bindings": {}, + "result": [1, 2, 3] + }, + { + "expr": "$symmetricDifference([{\"a\":1}, {\"b\":2}], [{\"b\":2}, {\"c\":3}])", + "data": null, + "bindings": {}, + "result": [{"a": 1}, {"c": 3}] + }, + { + "expr": "$symmetricDifference(Account.Order.Product.SKU, [\"0406654608\", \"12345\"])", + "dataset": "dataset5", + "bindings": {}, + "result": [ + "0406634348", + "040657863", + "0406654603", + "12345" + ] + } +] diff --git a/test/test-suite/groups/function-union/union.json b/test/test-suite/groups/function-union/union.json new file mode 100644 index 00000000..1c507958 --- /dev/null +++ b/test/test-suite/groups/function-union/union.json @@ -0,0 +1,38 @@ +[ + { + "expr": "$union([1,2,3], [3,4,5])", + "data": null, + "bindings": {}, + "result": [1,2,3,4,5] + }, + { + "expr": "$union([1,2,3], 3)", + "data": null, + "bindings": {}, + "result": [1,2,3] + }, + { + "expr": "$union([1,2,3], nothing)", + "data": null, + "bindings": {}, + "result": [1,2,3] + }, + { + "expr": "$union(nothing, [1,2,3])", + "data": null, + "bindings": {}, + "result": [1,2,3] + }, + { + "expr": "$union(Account.Order.Product.SKU, [\"0406654608\", \"12345\"])", + "dataset": "dataset5", + "bindings": {}, + "result": [ + "0406654608", + "0406634348", + "040657863", + "0406654603", + "12345" + ] + } +]