SetOperations.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import Utils from "../Utils.js";
  2. /**
  3. * Set operations.
  4. *
  5. * @author d98762625 [d98762625@gmail.com]
  6. * @copyright Crown Copyright 2018
  7. * @license APache-2.0
  8. *
  9. * @namespace
  10. */
  11. class SetOps {
  12. /**
  13. * Set default options for operation
  14. */
  15. constructor() {
  16. this._sampleDelimiter = "\\n\\n";
  17. this._operation = ["Union", "Intersection", "Set Difference", "Symmetric Difference", "Cartesian Product", "Power Set"];
  18. this._itemDelimiter = ",";
  19. }
  20. /**
  21. * Get operations array
  22. * @returns {String[]}
  23. */
  24. get OPERATION() {
  25. return this._operation;
  26. }
  27. /**
  28. * Get sample delimiter
  29. * @returns {String}
  30. */
  31. get SAMPLE_DELIMITER() {
  32. return this._sampleDelimiter;
  33. }
  34. /**
  35. * Get item delimiter
  36. * @returns {String}
  37. */
  38. get ITEM_DELIMITER() {
  39. return this._itemDelimiter;
  40. }
  41. /**
  42. * Run the configured set operation.
  43. *
  44. * @param {String} input
  45. * @param {String[]} args
  46. * @returns {html}
  47. */
  48. runSetOperation(input, args) {
  49. const [sampleDelim, itemDelimiter, operation] = args;
  50. const sets = input.split(sampleDelim);
  51. if (!sets || (sets.length !== 2 && operation !== "Power Set") || (sets.length !== 1 && operation === "Power Set")) {
  52. return "Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?";
  53. }
  54. if (this._operation.indexOf(operation) === -1) {
  55. return "Invalid 'Operation' option.";
  56. }
  57. let result = {
  58. "Union": this.runUnion,
  59. "Intersection": this.runIntersect,
  60. "Set Difference": this.runSetDifference,
  61. "Symmetric Difference": this.runSymmetricDifference,
  62. "Cartesian Product": this.runCartesianProduct,
  63. "Power Set": this.runPowerSet(itemDelimiter),
  64. }[operation]
  65. .apply(this, sets.map(s => s.split(itemDelimiter)));
  66. // Formatting issues due to the nested characteristics of power set.
  67. if (operation === "Power Set") {
  68. result = result.map(i => `${i}\n`).join("");
  69. } else {
  70. result = result.join(itemDelimiter);
  71. }
  72. return Utils.escapeHtml(result);
  73. }
  74. /**
  75. * Get the union of the two sets.
  76. *
  77. * @param {Object[]} a
  78. * @param {Object[]} b
  79. * @returns {Object[]}
  80. */
  81. runUnion(a, b) {
  82. const result = {};
  83. /**
  84. * Only add non-existing items
  85. * @param {Object} hash
  86. */
  87. const addUnique = (hash) => (item) => {
  88. if (!hash[item]) {
  89. hash[item] = true;
  90. }
  91. };
  92. a.map(addUnique(result));
  93. b.map(addUnique(result));
  94. return Object.keys(result);
  95. }
  96. /**
  97. * Get the intersection of the two sets.
  98. * @param {Object[]} a
  99. * @param {Object[]} b
  100. * @returns {Object[]}
  101. */
  102. runIntersect(a, b) {
  103. return a.filter((item) => {
  104. return b.indexOf(item) > -1;
  105. });
  106. }
  107. /**
  108. * Get elements in set a that are not in set b
  109. * @param {Object[]} a
  110. * @param {Object[]} b
  111. * @returns {Object[]}
  112. */
  113. runSetDifference(a, b) {
  114. return a.filter((item) => {
  115. return b.indexOf(item) === -1;
  116. });
  117. }
  118. /**
  119. * Get elements of each set that aren't in the other set.
  120. * @param {Object[]} a
  121. * @param {Object[]} b
  122. * @return {Object}
  123. */
  124. runSymmetricDifference(a, b) {
  125. return this.runSetDifference(a,b)
  126. .concat(this.runSetDifference(b, a));
  127. }
  128. /**
  129. *
  130. * @param {*} a
  131. * @param {*} b
  132. */
  133. runCartesianProduct(a, b) {
  134. return Array(Math.max(a.length, b.length))
  135. .fill(null)
  136. .map((item, index) => `(${a[index] || undefined},${b[index] || undefined})`);
  137. }
  138. /**
  139. *
  140. * @param {*} a
  141. */
  142. runPowerSet(delimiter) {
  143. return function(a) {
  144. // empty array items getting picked up
  145. a = a.filter(i => i.length);
  146. if (!a.length) {
  147. return [];
  148. }
  149. /**
  150. *
  151. * @param {*} dec
  152. */
  153. const toBinary = (dec) => (dec >>> 0).toString(2);
  154. const result = new Set();
  155. const maxBinaryValue = parseInt(Number(a.map(i => "1").reduce((p, c) => p + c)), 2);
  156. const binaries = [...Array(maxBinaryValue + 1).keys()]
  157. .map(toBinary)
  158. .map(i => i.padStart(toBinary(maxBinaryValue).length, "0"));
  159. binaries.forEach((binary) => {
  160. const split = binary.split("");
  161. result.add(a.filter((item, index) => split[index] === "1"));
  162. });
  163. // map for formatting & put in length order.
  164. return [...result].map(r => r.join(delimiter)).sort((a, b) => a.length - b.length);
  165. };
  166. }
  167. }
  168. export default new SetOps();