|
@@ -0,0 +1,181 @@
|
|
|
|
+import Utils from "../Utils.js";
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+class SetOps {
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+ constructor() {
|
|
|
|
+ this._sampleDelimiter = "\\n\\n";
|
|
|
|
+ this._operation = ["Union", "Intersection", "Set Difference", "Symmetric Difference", "Cartesian Product", "Power Set"];
|
|
|
|
+ this._itemDelimiter = ",";
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+ get OPERATION() {
|
|
|
|
+ return this._operation;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+ get SAMPLE_DELIMITER() {
|
|
|
|
+ return this._sampleDelimiter;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+ get ITEM_DELIMITER() {
|
|
|
|
+ return this._itemDelimiter;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param {*} input
|
|
|
|
+ * @param {*} args
|
|
|
|
+ */
|
|
|
|
+ runSetOperation(input, args) {
|
|
|
|
+ const [sampleDelim, itemDelimiter, operation] = args;
|
|
|
|
+ const sets = input.split(sampleDelim);
|
|
|
|
+
|
|
|
|
+ if (!sets || (sets.length !== 2 && operation !== "Power Set") || (sets.length !== 1 && operation === "Power Set")) {
|
|
|
|
+ return "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?";
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this._operation.indexOf(operation) === -1) {
|
|
|
|
+ return "Invalid 'Operation' option.";
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let result = {
|
|
|
|
+ Union: this.runUnion,
|
|
|
|
+ Intersection: this.runIntersect,
|
|
|
|
+ "Set Difference": this.runSetDifference,
|
|
|
|
+ "Symmetric Difference": this.runSymmetricDifference,
|
|
|
|
+ "Cartesian Product": this.runCartesianProduct,
|
|
|
|
+ "Power Set": this.runPowerSet(itemDelimiter),
|
|
|
|
+ }[operation]
|
|
|
|
+ .apply(null, sets.map(s => s.split(itemDelimiter)));
|
|
|
|
+
|
|
|
|
+ // Formatting issues due to the nested characteristics of power set.
|
|
|
|
+ if (operation === "Power Set") {
|
|
|
|
+ result = result.map(i => `${i}\n`).join("");
|
|
|
|
+ } else {
|
|
|
|
+ result = result.join(itemDelimiter);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return Utils.escapeHtml(result);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param {*} a
|
|
|
|
+ * @param {*} a
|
|
|
|
+ */
|
|
|
|
+ runUnion(a, b) {
|
|
|
|
+
|
|
|
|
+ const result = {};
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param {*} r
|
|
|
|
+ */
|
|
|
|
+ const addUnique = (hash) => (item) => {
|
|
|
|
+ if (!hash[item]) {
|
|
|
|
+ hash[item] = true;
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ a.map(addUnique(result));
|
|
|
|
+ b.map(addUnique(result));
|
|
|
|
+
|
|
|
|
+ return Object.keys(result);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param {*} a
|
|
|
|
+ * @param {*} b
|
|
|
|
+ */
|
|
|
|
+ runIntersect(a, b) {
|
|
|
|
+ return a.filter((item) => {
|
|
|
|
+ return b.indexOf(item) > -1;
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param {*} a
|
|
|
|
+ * @param {*} b
|
|
|
|
+ */
|
|
|
|
+ runSetDifference(a, b) {
|
|
|
|
+ return a.filter((item) => {
|
|
|
|
+ return b.indexOf(item) === -1;
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param {*} a
|
|
|
|
+ * @param {*} b
|
|
|
|
+ */
|
|
|
|
+ runSymmetricDifference(a, b) {
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param {*} refArray
|
|
|
|
+ */
|
|
|
|
+ const getDifference = (refArray) => (item) => {
|
|
|
|
+ return refArray.indexOf(item) === -1;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return a
|
|
|
|
+ .filter(getDifference(b))
|
|
|
|
+ .concat(b.filter(getDifference(a)));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param {*} a
|
|
|
|
+ * @param {*} b
|
|
|
|
+ */
|
|
|
|
+ runCartesianProduct(a, b) {
|
|
|
|
+ return Array(Math.max(a.length, b.length))
|
|
|
|
+ .fill(null)
|
|
|
|
+ .map((item, index) => `(${a[index] || undefined},${b[index] || undefined})`);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param {*} a
|
|
|
|
+ */
|
|
|
|
+ runPowerSet(delimiter) {
|
|
|
|
+ return function(a) {
|
|
|
|
+ /**
|
|
|
|
+ *
|
|
|
|
+ * @param {*} dec
|
|
|
|
+ */
|
|
|
|
+ const toBinary = (dec) => (dec >>> 0).toString(2);
|
|
|
|
+
|
|
|
|
+ const result = new Set();
|
|
|
|
+ const maxBinaryValue = parseInt(Number(a.map(i => "1").reduce((p, c) => p + c)), 2);
|
|
|
|
+ const binaries = [...Array(maxBinaryValue + 1).keys()]
|
|
|
|
+ .map(toBinary)
|
|
|
|
+ .map(i => i.padStart(toBinary(maxBinaryValue).length, "0"));
|
|
|
|
+
|
|
|
|
+ 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(delimiter)).sort((a, b) => a.length - b.length);
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+export default new SetOps();
|