Kaynağa Gözat

Add other set operations

d98762625 7 yıl önce
ebeveyn
işleme
adc4f78e99

+ 5 - 1
src/core/config/Categories.js

@@ -120,7 +120,11 @@ const Categories = [
         name: "Arithmetic / Logic",
         ops: [
             "Set Union",
-            "Set Intersection"
+            "Set Intersection",
+            "Set Difference",
+            "Symmetric Difference",
+            "Cartesian Product",
+            "Power Set",
     //         "XOR",
     //         "XOR Brute Force",
     //         "OR",

+ 52 - 0
src/core/config/OperationConfig.json

@@ -1,4 +1,23 @@
 {
+    "Cartesian Product": {
+        "module": "Default",
+        "description": "Get the cartesian product of two sets",
+        "inputType": "string",
+        "outputType": "string",
+        "flowControl": false,
+        "args": [
+            {
+                "name": "Sample delimiter",
+                "type": "binaryString",
+                "value": "\\n\\n"
+            },
+            {
+                "name": "Item delimiter",
+                "type": "binaryString",
+                "value": ","
+            }
+        ]
+    },
     "From Base32": {
         "module": "Default",
         "description": "Base32 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7.",
@@ -155,6 +174,20 @@
             }
         ]
     },
+    "Power Set": {
+        "module": "Default",
+        "description": "Generate the power set of a set",
+        "inputType": "string",
+        "outputType": "string",
+        "flowControl": false,
+        "args": [
+            {
+                "name": "Item delimiter",
+                "type": "binaryString",
+                "value": ","
+            }
+        ]
+    },
     "Raw Deflate": {
         "module": "Default",
         "description": "Compresses data using the deflate algorithm with no headers.",
@@ -305,6 +338,25 @@
             }
         ]
     },
+    "Symmetric Difference": {
+        "module": "Default",
+        "description": "Get the symmetric difference of two sets",
+        "inputType": "string",
+        "outputType": "string",
+        "flowControl": false,
+        "args": [
+            {
+                "name": "Sample delimiter",
+                "type": "binaryString",
+                "value": "\\n\\n"
+            },
+            {
+                "name": "Item delimiter",
+                "type": "binaryString",
+                "value": ","
+            }
+        ]
+    },
     "To Base32": {
         "module": "Default",
         "description": "Base32 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. It uses a smaller set of characters than Base64, usually the uppercase alphabet and the numbers 2 to 7.",

+ 6 - 0
src/core/config/modules/Default.mjs

@@ -5,15 +5,18 @@
 * @copyright Crown Copyright 2018
 * @license Apache-2.0
 */
+import CartesianProduct from "../../operations/CartesianProduct";
 import FromBase32 from "../../operations/FromBase32";
 import FromBase64 from "../../operations/FromBase64";
 import FromHex from "../../operations/FromHex";
+import PowerSet from "../../operations/PowerSet";
 import RawDeflate from "../../operations/RawDeflate";
 import SetDifference from "../../operations/SetDifference";
 import SetIntersection from "../../operations/SetIntersection";
 import SetOps from "../../operations/SetOps";
 import SetUnion from "../../operations/SetUnion";
 import ShowBase64Offsets from "../../operations/ShowBase64Offsets";
+import SymmetricDifference from "../../operations/SymmetricDifference";
 import ToBase32 from "../../operations/ToBase32";
 import ToBase64 from "../../operations/ToBase64";
 import ToHex from "../../operations/ToHex";
@@ -21,15 +24,18 @@ import ToHex from "../../operations/ToHex";
 const OpModules = typeof self === "undefined" ? {} : self.OpModules || {};
 
 OpModules.Default = {
+    "Cartesian Product": CartesianProduct,
     "From Base32": FromBase32,
     "From Base64": FromBase64,
     "From Hex": FromHex,
+    "Power Set": PowerSet,
     "Raw Deflate": RawDeflate,
     "Set Difference": SetDifference,
     "Set Intersection": SetIntersection,
     "": SetOps,
     "Set Union": SetUnion,
     "Show Base64 offsets": ShowBase64Offsets,
+    "Symmetric Difference": SymmetricDifference,
     "To Base32": ToBase32,
     "To Base64": ToBase64,
     "To Hex": ToHex,

+ 84 - 0
src/core/operations/CartesianProduct.mjs

@@ -0,0 +1,84 @@
+/**
+ * @author d98762625 [d98762625@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+import Operation from "../Operation";
+
+/**
+ * Set cartesian product operation
+ */
+class CartesianProduct extends Operation {
+
+    /**
+     * Cartesian Product constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Cartesian Product";
+        this.module = "Default";
+        this.description = "Get the cartesian product of two sets";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                name: "Sample delimiter",
+                type: "binaryString",
+                value: Utils.escapeHtml("\\n\\n")
+            },
+            {
+                name: "Item delimiter",
+                type: "binaryString",
+                value: ","
+            },
+        ];
+    }
+
+    /**
+     * Validate input length
+     * @param {Object[]} sets
+     * @throws {Error} if not two sets
+     */
+    validateSampleNumbers(sets) {
+        if (!sets || (sets.length !== 2)) {
+            throw "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?";
+        }
+    }
+
+    /**
+     * Run the product operation
+     * @param input
+     * @param args
+     */
+    run(input, args) {
+        [this.sampleDelim, this.itemDelimiter] = args;
+        const sets = input.split(this.sampleDelim);
+
+        try {
+            this.validateSampleNumbers(sets);
+        } catch (e) {
+            return e;
+        }
+
+        return Utils.escapeHtml(this.runCartesianProduct(...sets.map(s => s.split(this.itemDelimiter))));
+    }
+
+    /**
+    * Return the cartesian product of the two inputted sets.
+    *
+    * @param {Object[]} a
+    * @param {Object[]} b
+    * @returns {String[]}
+    */
+    runCartesianProduct(a, b) {
+        return Array(Math.max(a.length, b.length))
+            .fill(null)
+            .map((item, index) => `(${a[index] || undefined},${b[index] || undefined})`)
+            .join(this.itemDelimiter);
+    }
+}
+
+export default CartesianProduct;

+ 91 - 0
src/core/operations/PowerSet.mjs

@@ -0,0 +1,91 @@
+/**
+ * @author d98762625 [d98762625@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+import Operation from "../Operation";
+
+/**
+ * Power Set operation
+ */
+class PowerSet extends Operation {
+
+    /**
+     * Power set constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Power Set";
+        this.module = "Default";
+        this.description = "Generate the power set of a set";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                name: "Item delimiter",
+                type: "binaryString",
+                value: ","
+            },
+        ];
+    }
+
+    /**
+     * Generate the power set
+     * @param input
+     * @param args
+     */
+    run(input, args) {
+        [this.itemDelimiter] = args;
+        // Split and filter empty strings
+        const inputArray = input.split(this.itemDelimiter).filter(a => a);
+
+        if (inputArray.length) {
+            return Utils.escapeHtml(this.runPowerSet(inputArray));
+        }
+
+        return "";
+    }
+
+    /**
+     * Return the power set of the inputted set.
+     *
+     * @param {Object[]} a
+     * @returns {Object[]}
+     */
+    runPowerSet(a) {
+        // empty array items getting picked up
+        a = a.filter(i => i.length);
+        if (!a.length) {
+            return [];
+        }
+
+        /**
+         * Decimal to binary function
+         * @param {*} dec
+         */
+        const toBinary = (dec) => (dec >>> 0).toString(2);
+        const result = new Set();
+        // Get the decimal number to make a binary as long as the input
+        const maxBinaryValue = parseInt(Number(a.map(i => "1").reduce((p, c) => p + c)), 2);
+        // Make an array of each binary number from 0 to maximum
+        const binaries = [...Array(maxBinaryValue + 1).keys()]
+            .map(toBinary)
+            .map(i => i.padStart(toBinary(maxBinaryValue).length, "0"));
+
+        // XOR the input with each binary to get each unique permutation
+        binaries.forEach((binary) => {
+            const split = binary.split("");
+            result.add(a.filter((item, index) => split[index] === "1"));
+        });
+
+        // map for formatting & put in length order.
+        return [...result]
+            .map(r => r.join(this.itemDelimiter)).sort((a, b) => a.length - b.length)
+            .map(i => `${i}\n`).join("");
+    }
+}
+
+export default PowerSet;

+ 1 - 1
src/core/operations/SetDifference.mjs

@@ -63,7 +63,7 @@ class SetDifference extends Operation {
             return e;
         }
 
-        return Utils.escapeHtml(this.runSetDifferencez(...sets.map(s => s.split(this.itemDelimiter))));
+        return Utils.escapeHtml(this.runSetDifference(...sets.map(s => s.split(this.itemDelimiter))));
     }
 
     /**

+ 97 - 0
src/core/operations/SymmetricDifference.mjs

@@ -0,0 +1,97 @@
+/**
+ * @author d98762625 [d98762625@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils";
+import Operation from "../Operation";
+
+/**
+ * Set Symmetric Difference operation
+ */
+class SymmetricDifference extends Operation {
+
+    /**
+     * Symmetric Difference constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Symmetric Difference";
+        this.module = "Default";
+        this.description = "Get the symmetric difference of two sets";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                name: "Sample delimiter",
+                type: "binaryString",
+                value: Utils.escapeHtml("\\n\\n")
+            },
+            {
+                name: "Item delimiter",
+                type: "binaryString",
+                value: ","
+            },
+        ];
+    }
+
+    /**
+     * Validate input length
+     * @param {Object[]} sets
+     * @throws {Error} if not two sets
+     */
+    validateSampleNumbers(sets) {
+        if (!sets || (sets.length !== 2)) {
+            throw "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?";
+        }
+    }
+
+    /**
+     * Run the difference operation
+     * @param input
+     * @param args
+     */
+    run(input, args) {
+        [this.sampleDelim, this.itemDelimiter] = args;
+        const sets = input.split(this.sampleDelim);
+
+        try {
+            this.validateSampleNumbers(sets);
+        } catch (e) {
+            return e;
+        }
+
+        return Utils.escapeHtml(this.runSymmetricDifference(...sets.map(s => s.split(this.itemDelimiter))));
+    }
+
+    /**
+     * Get elements in set a that are not in set b
+     *
+     * @param {Object[]} a
+     * @param {Object[]} b
+     * @returns {Object[]}
+     */
+    runSetDifference(a, b) {
+        return a.filter((item) => {
+            return b.indexOf(item) === -1;
+        });
+    }
+
+    /**
+     * Get elements of each set that aren't in the other set.
+     *
+     * @param {Object[]} a
+     * @param {Object[]} b
+     * @return {Object[]}
+     */
+    runSymmetricDifference(a, b) {
+        return this.runSetDifference(a, b)
+            .concat(this.runSetDifference(b, a))
+            .join(this.itemDelimiter);
+    }
+
+}
+
+export default SymmetricDifference;

+ 6 - 0
src/core/operations/index.mjs

@@ -5,11 +5,13 @@
 * @copyright Crown Copyright 2018
 * @license Apache-2.0
 */
+import CartesianProduct from "./CartesianProduct";
 import FromBase32 from "./FromBase32";
 import FromBase64 from "./FromBase64";
 import FromHex from "./FromHex";
 import Gunzip from "./Gunzip";
 import Gzip from "./Gzip";
+import PowerSet from "./PowerSet";
 import RawDeflate from "./RawDeflate";
 import RawInflate from "./RawInflate";
 import SetDifference from "./SetDifference";
@@ -17,6 +19,7 @@ import SetIntersection from "./SetIntersection";
 import SetOps from "./SetOps";
 import SetUnion from "./SetUnion";
 import ShowBase64Offsets from "./ShowBase64Offsets";
+import SymmetricDifference from "./SymmetricDifference";
 import ToBase32 from "./ToBase32";
 import ToBase64 from "./ToBase64";
 import ToHex from "./ToHex";
@@ -26,11 +29,13 @@ import ZlibDeflate from "./ZlibDeflate";
 import ZlibInflate from "./ZlibInflate";
 
 export {
+    CartesianProduct,
     FromBase32,
     FromBase64,
     FromHex,
     Gunzip,
     Gzip,
+    PowerSet,
     RawDeflate,
     RawInflate,
     SetDifference,
@@ -38,6 +43,7 @@ export {
     SetOps,
     SetUnion,
     ShowBase64Offsets,
+    SymmetricDifference,
     ToBase32,
     ToBase64,
     ToHex,

+ 3 - 0
test/index.mjs

@@ -50,6 +50,9 @@ import "./tests/operations/Base64";
 import "./tests/operations/SetUnion";
 import "./tests/operations/SetIntersection";
 import "./tests/operations/SetDifference";
+import "./tests/operations/SymmetricDifference";
+import "./tests/operations/CartesianProduct";
+import "./tests/operations/PowerSet";
 
 let allTestsPassing = true;
 const testStatusCounts = {

+ 78 - 0
test/tests/operations/CartesianProduct.mjs

@@ -0,0 +1,78 @@
+/**
+ * Cartesian Product tests.
+ *
+ * @author d98762625
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister";
+
+TestRegister.addTests([
+    {
+        name: "Cartesian Product",
+        input: "1 2 3 4 5\n\na b c d e",
+        expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e)",
+        recipeConfig: [
+            {
+                op: "Cartesian Product",
+                args: ["\n\n", " "],
+            },
+        ],
+    },
+    {
+        name: "Cartesian Product: wrong sample count",
+        input: "1 2\n\n3 4 5\n\na b c d e",
+        expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+        recipeConfig: [
+            {
+                op: "Cartesian Product",
+                args: ["\n\n", " "],
+            },
+        ],
+    },
+    {
+        name: "Cartesian Product: too many on left",
+        input: "1 2 3 4 5 6\n\na b c d e",
+        expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e) (6,undefined)",
+        recipeConfig: [
+            {
+                op: "Cartesian Product",
+                args: ["\n\n", " "],
+            },
+        ],
+    },
+    {
+        name: "Cartesian Product: too many on right",
+        input: "1 2 3 4 5\n\na b c d e f",
+        expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e) (undefined,f)",
+        recipeConfig: [
+            {
+                op: "Cartesian Product",
+                args: ["\n\n", " "],
+            },
+        ],
+    },
+    {
+        name: "Cartesian Product: item delimiter",
+        input: "1-2-3-4-5\n\na-b-c-d-e",
+        expectedOutput: "(1,a)-(2,b)-(3,c)-(4,d)-(5,e)",
+        recipeConfig: [
+            {
+                op: "Cartesian Product",
+                args: ["\n\n", "-"],
+            },
+        ],
+    },
+    {
+        name: "Cartesian Product: sample delimiter",
+        input: "1 2 3 4 5_a b c d e",
+        expectedOutput: "(1,a) (2,b) (3,c) (4,d) (5,e)",
+        recipeConfig: [
+            {
+                op: "Cartesian Product",
+                args: ["_", " "],
+            },
+        ],
+    },
+]);

+ 34 - 0
test/tests/operations/PowerSet.mjs

@@ -0,0 +1,34 @@
+/**
+ * Power Set tests.
+ *
+ * @author d98762625
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister";
+
+TestRegister.addTests([
+    {
+        name: "Power set: nothing",
+        input: "",
+        expectedOutput: "",
+        recipeConfig: [
+            {
+                op: "Power Set",
+                args: [","],
+            },
+        ],
+    },
+    {
+        name: "Power set",
+        input: "1 2 4",
+        expectedOutput: "\n4\n2\n1\n2 4\n1 4\n1 2\n1 2 4\n",
+        recipeConfig: [
+            {
+                op: "Power Set",
+                args: [" "],
+            },
+        ],
+    },
+]);

+ 56 - 0
test/tests/operations/SymmetricDifference.mjs

@@ -0,0 +1,56 @@
+/**
+ * Symmetric difference tests.
+ *
+ * @author d98762625
+ *
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../TestRegister";
+
+TestRegister.addTests([
+    {
+        name: "Symmetric Difference",
+        input: "1 2 3 4 5\n\n3 4 5 6 7",
+        expectedOutput: "1 2 6 7",
+        recipeConfig: [
+            {
+                op: "Symmetric Difference",
+                args: ["\n\n", " "],
+            },
+        ],
+    },
+    {
+        name: "Symmetric Difference: wrong sample count",
+        input: "1 2\n\n3 4 5\n\n3 4 5 6 7",
+        expectedOutput: "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?",
+        recipeConfig: [
+            {
+                op: "Symmetric Difference",
+                args: ["\n\n", " "],
+            },
+        ],
+    },
+    {
+        name: "Symmetric Difference: item delimiter",
+        input: "a_b_c_d_e\n\nc_d_e_f_g",
+        expectedOutput: "a_b_f_g",
+        recipeConfig: [
+            {
+                op: "Symmetric Difference",
+                args: ["\n\n", "_"],
+            },
+        ],
+    },
+    {
+        name: "Symmetric Difference: sample delimiter",
+        input: "a_b_c_d_eAAAAAc_d_e_f_g",
+        expectedOutput: "a_b_f_g",
+        recipeConfig: [
+            {
+                op: "Symmetric Difference",
+                args: ["AAAAA", "_"],
+            },
+        ],
+    },
+]);