index.js

  1. // //////////////////////////////////////////////////////////////////////////////// //
  2. // MIT License
  3. //
  4. // Copyright (c) 2018 Jan Küster
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in all
  14. // copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. // SOFTWARE.
  23. //
  24. // //////////////////////////////////////////////////////////////////////////////// //
  25. /* global self */
  26. // //////////////////////////////////////////////////////////////////////////////// //
  27. // //
  28. // INTERNAL //
  29. // //
  30. // //////////////////////////////////////////////////////////////////////////////// //
  31. /**
  32. * @private detect current environment's globalThis to enable cross-env usage
  33. */
  34. const scope = (() => {
  35. if (typeof self !== 'undefined') { return self }
  36. if (typeof window !== 'undefined') { return window }
  37. if (typeof global !== 'undefined') { return global }
  38. throw new Error('unable to locate global object')
  39. })()
  40. /**
  41. * @private checks all rules in list tro be a function @private
  42. */
  43. const checkRules = (rules) => {
  44. rules.forEach(rule => {
  45. if (typeof rule !== 'function') {
  46. throw new Error(`Expected [rule] to be typeof [function], got [${typeof value}]`)
  47. }
  48. })
  49. return true
  50. }
  51. /**
  52. * @private checks, whether an Object is a Set
  53. * @return {boolean}
  54. */
  55. const isSet = s => Object.prototype.toString.call(s) === '[object Set]'
  56. /**
  57. * @private checks, whether a given value is a Set instance @private
  58. */
  59. const checkSet = (set) => {
  60. if (!set || !set.constructor || !isSet(set) || !(set instanceof scope.Set)) {
  61. throw new Error(`Expected [set] to be instanceof [${scope.Set.name}], got [${set && set.constructor}]`)
  62. }
  63. return true
  64. }
  65. /**
  66. * @private checks all values to be a Set-instance @private
  67. */
  68. const checkSets = (sets) => sets.every(s => checkSet(s))
  69. /**
  70. * @private checks arguments length and raises error if not given length
  71. */
  72. const checkArgsLength = (args, length = 1) => {
  73. if (!args || args.length !== length) {
  74. throw new Error(`The function must be given exactly ${length} argument.`)
  75. }
  76. return true
  77. }
  78. /**
  79. * A decorator which, given an arbitrary set function,
  80. * produces the corresponding binary operation.
  81. * @private
  82. */
  83. const arbitraryToBinary = (arbitraryFunc) => {
  84. return function binaryFunc (...args) {
  85. checkArgsLength(args, 1)
  86. const set = args[0]
  87. return arbitraryFunc(this, set)
  88. }
  89. }
  90. /**
  91. * @private contains references to the original Set functions
  92. */
  93. const originals = {
  94. /**
  95. * @private The original Set reference.
  96. */
  97. constructor: scope.Set,
  98. /**
  99. * @private The original add function.
  100. */
  101. add: scope.Set.prototype.add,
  102. /**
  103. * @private The original has function reference.
  104. */
  105. has: scope.Set.prototype.has
  106. }
  107. // //////////////////////////////////////////////////////////////////////////////// //
  108. // //
  109. // OVERRIDES //
  110. // //
  111. // //////////////////////////////////////////////////////////////////////////////// //
  112. scope.Set.prototype.add =
  113. /**
  114. * Adds a value to the set. If the set already contains the value, nothing happens.
  115. * Overrides Set.prototype.add.
  116. * @name Set.prototype.add
  117. * @function
  118. * @throws Error if rules function exists and {value} failed the rules check.
  119. * @param value {*}- Required. Any arbitrary value to be added to the set.
  120. * @returns {Set} the Set object
  121. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add
  122. */
  123. function add (value) {
  124. if (this.rulesFct && !this.rulesFct.call(null, value)) {
  125. throw new Error(`Value [${value}] does not match ruleset.`)
  126. }
  127. // in case we add another set, we actually need to (recursively) check
  128. // whether the set is already included, since the original add function
  129. // only checks for uniqueness on a reference level
  130. if (isSet(value) && this.has(value)) {
  131. return this
  132. }
  133. return originals.add.call(this, value)
  134. }
  135. /**
  136. * Resolves an element's inner structure to make it comparable by JSON.stringify.
  137. * @private
  138. */
  139. function resolve (obj, circ = new originals.constructor([obj])) {
  140. if (typeof obj === 'undefined' ||
  141. typeof obj === 'string' ||
  142. typeof obj === 'number' ||
  143. typeof obj === 'boolean' ||
  144. obj === null) {
  145. return obj
  146. }
  147. // if we have a set we convert it to an Array and continue treating it as such
  148. if (isSet(obj)) {
  149. obj = Array.from(obj)
  150. }
  151. if (typeof obj === 'function') {
  152. const fctObj = { fctStr: String(obj).replace(/\s+/g, '') } // function body to string
  153. // resolve all function properties / attached references
  154. fctObj.refs = Object.getOwnPropertyNames(obj).map(key => originals.has.call(circ, obj[key]) ? 'circular' : resolve(obj[key], circ))
  155. return fctObj
  156. }
  157. const isArray = Array.isArray(obj)
  158. if (typeof obj !== 'object' && !isArray) {
  159. return obj
  160. }
  161. // add obj to check for
  162. // circular references
  163. circ.add(obj)
  164. if (isArray) {
  165. return obj.map(el => originals.has.call(circ, el) ? 'circular' : resolve(el, circ))
  166. }
  167. const copy = {}
  168. Object.getOwnPropertyNames(obj)
  169. .sort((a, b) => a.localeCompare(b))
  170. .forEach(key => {
  171. copy[key] = originals.has.call(circ, obj[key]) ? 'circular' : resolve(obj[key], circ)
  172. })
  173. return copy
  174. }
  175. /**
  176. * Checks if the current set instance contains a given value by recursive deep compare.
  177. * Overrides the original Set.prototype.has.
  178. * The check is recursive and respects
  179. * <ul>
  180. * <li>primitive types</li>
  181. * <li>complex types, such as Objects or Arrays</li>
  182. * <li>nested Objects and cyclic references</li>
  183. * <li>functions</li>
  184. * <li>functions with properties attached</li>
  185. * <li>sets, sets of sets</li>
  186. * </ul>
  187. *
  188. * Note, that functions will be checked against their whitespace-trimmed bodies, which can return false negatives,
  189. * if for example a comment is added to the compare function that not exists in the original function.
  190. *
  191. * @function
  192. * @name Set.prototype.has
  193. * @example
  194. * const a = Set.from({ a:true, b:false })
  195. * a.has({ b:false, a:true }) // true
  196. * a.has({ b:false, a:false }) // false
  197. * @param value {*} - The value to be checked.
  198. * @returns {boolean} - True, if the value is contained by the set. False, if otherwise.
  199. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has
  200. */
  201. scope.Set.prototype.has = function has (value) {
  202. const valType = typeof value
  203. if (valType === 'string' || valType === 'number' || valType === 'boolean') {
  204. return originals.has.call(this, value)
  205. }
  206. const iterator = this.values()
  207. let element
  208. while ((element = iterator.next().value) !== undefined) {
  209. const elType = typeof element
  210. if (elType !== valType) {
  211. return false
  212. }
  213. const setCompare = isSet(element) && isSet(value)
  214. // if both point to the same reference
  215. if (element === value) {
  216. return true
  217. } else
  218. // if we want to check if this set has a set with the
  219. // same elements as the given set in the argument,
  220. // we need to check for equality of all elements of this set
  221. // and the argument set
  222. if (setCompare && element.equal(value)) {
  223. return true
  224. } else
  225. // - if we want to check if ordered pairs (represented as arrays),
  226. // are equal, we resolve their children and compare their strings.
  227. // - For all nested objects we recursively create a "sorted"
  228. // version of both and compare their strings.
  229. // - functions are string-ed and their properties are resolved
  230. // like objects
  231. if ((elType === 'function' && valType === 'function') ||
  232. (!setCompare && elType === 'object' && valType === 'object') ||
  233. (Array.isArray(element) && Array.isArray(value))) {
  234. const sortedElmnt = resolve(element)
  235. const sortedValue = resolve(value)
  236. if (JSON.stringify(sortedElmnt) === JSON.stringify(sortedValue)) {
  237. return true
  238. }
  239. }
  240. }
  241. // and if nothing has matched, we assume
  242. // that it is not contained in this set
  243. return false
  244. }
  245. // //////////////////////////////////////////////////////////////////////////////// //
  246. // //
  247. // PROTOTYPES //
  248. // //
  249. // //////////////////////////////////////////////////////////////////////////////// //
  250. scope.Set.prototype.rules =
  251. /**
  252. * Pass a function that dictates the rules for elements to be part of this set.
  253. * Use without args to get the current rules function.
  254. * <br>
  255. * A rules function needs to fulfill the following requirements:
  256. * <ul>
  257. * <li>Obtain a single element as argument</li>
  258. * <li>Check, if that element passes certain conditions</li>
  259. * <li>Return false if the element fails any condition</li>
  260. * <li>Otherwise return true</li>
  261. * </ul>
  262. * <br>
  263. * If a set contains a rules function (or a merge of many rules functions), the element will only be added to the set,
  264. * if it passes the rules check.
  265. * @function
  266. * @name Set.prototype.rules
  267. * @example
  268. * const isInt = n => Number.isInteger(n)
  269. * const integers = Set.from()
  270. * integers.rules(isInt)
  271. * integers.add(1) // OK, no error
  272. * integers.add(1.5) // throws error!
  273. * integers.add(1.0) // OK, because 1.0 === 1 in JS Number
  274. * @param value {Function} (Optional) a Function that obtains a single argument and returns either a truthy or falsey value.
  275. * @returns {Function|undefined} Returns the current rules Function or undefined if there is on rules function assigned.
  276. */
  277. function rules (value) {
  278. if (value) {
  279. checkRules([value])
  280. this.rulesFct = value
  281. }
  282. return this.rulesFct
  283. }
  284. scope.Set.prototype.toArray =
  285. /**
  286. * Creates an (unsorted) array from all elements of this set.
  287. * @function
  288. * @name Set.prototype.toArray
  289. * @example new Set([1, 2, 3, 4]).toArray() // [ 1, 2, 3, 4 ]
  290. * @returns {Array} Array containing all elements of this set in unsorted order.
  291. */
  292. function toArray () {
  293. const self = this
  294. const out = []
  295. out.length = self.size
  296. let count = 0
  297. self.forEach(value => {
  298. out[count++] = value
  299. })
  300. return out
  301. }
  302. scope.Set.prototype.any =
  303. /**
  304. * Returns an arbitrary element of this set.
  305. * Basically the first element, retrieved by iterator.next().value will be used.
  306. * @function
  307. * @name Set.prototype.any
  308. * @returns {*} An arbitrary element of the current set that could by of any type, depending on the elements of the set.
  309. */
  310. function any () {
  311. const self = this
  312. const iterator = self.values()
  313. return iterator.next().value
  314. }
  315. scope.Set.prototype.randomElement =
  316. /**
  317. * Returns a random element of this set.
  318. * One element of this set is chosen at random and returned. The probability distribution is uniform. Math.random() is used internally for this purpose.
  319. * @function
  320. * @name Set.prototype.randomElement
  321. * @returns {*} An element chosen randomly from the current set that could be of any type, depending on the elements of the set.
  322. */
  323. function randomElementUnary () {
  324. const array = this.toArray()
  325. const randomIndex = Math.floor(Math.random() * array.length)
  326. return array[randomIndex]
  327. }
  328. scope.Set.prototype.isSupersetOf =
  329. /**
  330. * Checks, whether the current set (this) is a superset of the given set.
  331. * A set A is superset of set B, if A contains all elements of B.
  332. * <br>
  333. * Expression: <code>A ⊇ B</code>
  334. * @function
  335. * @name Set.prototype.isSupersetOf
  336. * @example
  337. * const a = Set.from(1,2,3,4)
  338. * const b = Set.from(1,2,3)
  339. * const c = Set.from(1,2,3,4,5)
  340. * a.isSupersetOf(b) // true
  341. * a.isSupersetOf(c) // false
  342. * c.isSupersetOf(b) // true
  343. * @param set {Set} - A set instance of which this set is checked to be the superset.
  344. * @throws Throws an error, if the given set is not a set instance.
  345. * @returns {boolean} true if this set is the superset of the given set, otherwise false.
  346. * @see https://en.wikipedia.org/wiki/Subset
  347. */
  348. function isSupersetOf (set) {
  349. const iterator = set.values()
  350. let value
  351. while ((value = iterator.next().value) !== undefined) {
  352. if (!this.has(value)) return false
  353. }
  354. return true
  355. }
  356. scope.Set.prototype.isSubsetOf =
  357. /**
  358. * Checks, whether the current set (this) is a subset of the given set.
  359. * A set A is subset of set B, if B contains all elements of A.
  360. * <br>
  361. * Expression: <code>A ⊆ B</code>
  362. * <br>
  363. * If their sizes are also equal, they can be assumed as equal.
  364. * If their sizes are not equal, then A is called a proper subset of B.
  365. * @function
  366. * @name Set.prototype.isSubsetOf
  367. * @example
  368. * const a = Set.from(1,2,3,4)
  369. * const b = Set.from(1,2,3)
  370. * const c = Set.from(1,2,3,4,5)
  371. * a.isSubsetOf(b) // false
  372. * b.isSubsetOf(c) // true
  373. * c.isSubsetOf(a) // false
  374. * @param set {Set} - A set instance of which this set is checked to be the subset.
  375. * @throws Throws an error, if the given set is not a set instance.
  376. * @returns {boolean} - true if this set is the subset of the given set, false otherwise
  377. * @see https://en.wikipedia.org/wiki/Subset
  378. * @see Set.prototype.equal
  379. * @see Set.prototype.isProperSubsetOf
  380. */
  381. function isSubsetOf (set) {
  382. return set.isSupersetOf(this)
  383. }
  384. scope.Set.prototype.properSupersetOf =
  385. /**
  386. * Checks, whether the current set (this) is a proper superset of the given set.
  387. * A set A is a proper subset of set B, if A contains all elements of B and their sizes are not equal.
  388. * <br>
  389. * Expression: <code>A ⊃ B</code>
  390. * @function
  391. * @name Set.prototype.properSupersetOf
  392. * @param set {Set} - A set instance of which this set is checked to be the proper superset.
  393. * @returns {boolean}
  394. * @see https://en.wikipedia.org/wiki/Subset
  395. */
  396. function isProperSupersetOf (set) {
  397. return this.size !== set.size && this.isSupersetOf(set)
  398. }
  399. scope.Set.prototype.properSubsetOf =
  400. /**
  401. * Checks, whether the current set (this) is a proper subset of the given set.
  402. * A set A is a proper subset of set B, if B contains all elements of A and their sizes are not equal.
  403. * <br>
  404. * Expression: <code>A ⊂ B</code>
  405. * @function
  406. * @name Set.prototype.properSupersetOf
  407. * @param set {Set} - A set instance of which this set is checked to be the proper subset.
  408. * @returns {boolean}
  409. * @see https://en.wikipedia.org/wiki/Subset
  410. */
  411. function isProperSubsetOf (set) {
  412. return this.size !== set.size && this.isSubsetOf(set)
  413. }
  414. scope.Set.prototype.equal =
  415. /**
  416. * Checks, whether two sets are equal in terms of their contained elements.
  417. * Note: This implementation uses a deep object comparison in order to check for "sameness".
  418. * This allows also to check equality for more complex / nested structures without the restriction of interpreting
  419. * "sameness" as "being the exact same instance". If such an equality is desired, please use Set.prototype.equalSrict
  420. * @function
  421. * @name Set.prototype.equal
  422. * @example
  423. * const a = Set.from(1,2,3)
  424. * const b = Set.from(1,2,3.0) // note that 3.0 will evaluate to 3 here!
  425. * a === b // false
  426. * a.equal(b) // true
  427. * @example
  428. * const a = Set.from({ a:true, b:false })
  429. * const b = Set.from({ b:false, a:true })
  430. * a.equal(b) // true
  431. * @param set {Set} - A set instance, which this set is to be compared with.
  432. * @throws Throws an error if the given paramter is not a Set instance.
  433. * @returns {boolean} true, if all elements of this set equal to the elements of the given set.
  434. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
  435. * @see Set.prototype.isSubsetOf
  436. */
  437. function equal (set) {
  438. checkSet(set)
  439. if (this.size !== set.size) {
  440. return false
  441. }
  442. return this.isSubsetOf(set)
  443. }
  444. scope.Set.prototype.isEmpty =
  445. /**
  446. * Checks whether this set is the empty set.
  447. * A Set is empty if and only if it has no elements. This is the same thing as having size (cardinality) 0. The empty set is often denoted ∅ or {}.
  448. * @example
  449. * const A = new Set()
  450. * const B = new Set([])
  451. * const C = Set.from()
  452. * const D = Set.from(7)
  453. * A.isEmpty() // true
  454. * B.isEmpty() // true
  455. * C.isEmpty() // true
  456. * D.isEmpty() // false
  457. * @function
  458. * @name Set.prototype.isEmpty
  459. * @throws Throws an error if any arguments are given.
  460. * @returns {boolean}
  461. * @see https://en.wikipedia.org/wiki/Empty_set
  462. */
  463. function isEmptyUnary () {
  464. return this.size === 0
  465. }
  466. // //////////////////////////////////////////////////////////////////////////////// //
  467. // //
  468. // CONSTRUCTOR //
  469. // //
  470. // //////////////////////////////////////////////////////////////////////////////// //
  471. scope.Set =
  472. /**
  473. * Use <code>new Set(elements, rulesFct)</code> to create new sets. Alternatively you can use <code>Set.from</code>
  474. * @class
  475. * @name Set
  476. * @classdesc Extended version of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set">Set (MDN link)</a>
  477. * @param elements {array} - an Array of element.
  478. * @param rulesFct {function} - a function which every element added to the set needs to pass.
  479. * @see Set.from
  480. * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
  481. * @returns {Set} An instance of the extended version of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set">Set (MDN link)</a>
  482. */
  483. function Set (elements, rulesFct) {
  484. const original = new originals.constructor()
  485. if (rulesFct) {
  486. original.rules(rulesFct)
  487. }
  488. if (elements) { elements.forEach(element => original.add(element)) }
  489. return original
  490. }
  491. /**
  492. * The prototype is the original Set constructor
  493. * @type {contains}
  494. */
  495. scope.Set.prototype = originals.constructor.prototype
  496. // //////////////////////////////////////////////////////////////////////////////// //
  497. // //
  498. // STATICS //
  499. // //
  500. // //////////////////////////////////////////////////////////////////////////////// //
  501. scope.Set.from =
  502. /**
  503. * Creates a new Set from arbitrary arguments without the need of "new" and the array notation.
  504. * @function
  505. * @name Set.from
  506. * @example Set.from(1,2,3,4,5) // returns Set { 1, 2, 3, 4, 5 }
  507. * @example
  508. * const ints = Set.from(1,2,3)
  509. * const flts = Set.from(4.5, 5.6, 6.7)
  510. * Set.from(ints, flts) // returns Set { Set {1, 2, 3}, Set { 4.5, 5.6, 6.7 } }
  511. * @param args {...*} - values of any types / length (using comma notation or spread operator)
  512. * @returns {Set} A set containing the given argument values.
  513. */
  514. function from (...args) {
  515. return new Set([...args])
  516. }
  517. scope.Set.toSet =
  518. /**
  519. * Autowraps a value to a Set, unless it is already a Set.
  520. * @function
  521. * @name Set.toSet
  522. * @param value {*} - Any arbitrary value
  523. * @returns {Set} A Set containing the value or the value if it is already a Set.
  524. */
  525. function toSet (value) {
  526. return value instanceof Set ? value : Set.from(value)
  527. }
  528. scope.Set.copy =
  529. /**
  530. * Copies all elements of a given Set instance into a new Set and returns it.
  531. * <strong>It does not deep-clone the elements of the set.</strong>
  532. * @function
  533. * @name Set.copy
  534. * @throws Throws an error if the argument is not a Set instance.
  535. * @param set {Set} a set instance from which to copy from
  536. * @returns {Set} a new Set instance containing all elements of the source.
  537. */
  538. function copy (set) {
  539. checkSet(set)
  540. const c = new Set()
  541. set.forEach(el => c.add(el))
  542. return c
  543. }
  544. scope.Set.union =
  545. /**
  546. * Creates the set union of an arbitrary number of sets.
  547. * The union S of any number of sets M<sub>i</sub> is the set that consists of all elements of each M<sub>i</sub>.
  548. * <br>Expression: <code>∪ M = S</code>
  549. * <br>Example: <code>∪ {M_1, M_2, M_3} = S</code>
  550. * <br>Example: <code>∪ {A, B, C} = S</code>
  551. * <br>Example: <code>∪ {{0,4}, {1}, {9}} = {0,1,4,9}</code>
  552. * @example
  553. * const A = Set.from(0, 4)
  554. * const B = Set.from(1)
  555. * const C = Set.from(9)
  556. * Set.union(A, B, C) // Set { 0, 1, 4, 9 }
  557. * const M = [A, B, C]
  558. * Set.union(...M) // Set { 0, 1, 4, 9 }
  559. * @name Set.union
  560. * @function
  561. * @param args {...Set} - an arbitrary list of Set instances
  562. * @throws Throws an error if any of the arguments is not a Set instance.
  563. * @returns {Set} a Set instance with the unified elements of the given args.
  564. * @see https://en.wikipedia.org/wiki/Union_(set_theory)#Arbitrary_unions
  565. */
  566. function unionArbitrary (...args) {
  567. checkSets(args)
  568. const set3 = new Set()
  569. args.forEach(set => set.forEach(value => set3.add(value)))
  570. return set3
  571. }
  572. /**
  573. * Creates the set union of two sets.
  574. * The union of A and B is the set C that consists of all elements of A and B.
  575. * <br>Expression: <code>A ∪ B = C</code>
  576. * <br>Example: <code>{1,2} ∪ {1,7,8,9} = {1,2,7,8,9}</code>
  577. * @example
  578. * const A = Set.from(1, 2)
  579. * const B = Set.from(1, 7, 8, 9)
  580. * A.union(B) // Set { 1, 2, 7, 8, 9 }
  581. * @name Set.prototype.union
  582. * @function
  583. * @param args {set} - the other set to union with.
  584. * @throws Throws an error if there is not exactly one argument.
  585. * @throws Throws an error if the argument is not a Set instance.
  586. * @returns {Set} a Set instance with the unified elements of the given args.
  587. * @see https://en.wikipedia.org/wiki/Union_(set_theory)#Union_of_two_sets
  588. */
  589. scope.Set.prototype.union = arbitraryToBinary(scope.Set.union)
  590. scope.Set.intersection =
  591. /**
  592. * Creates the set intersection of an arbitrary number of sets.
  593. * The intersection S of any number of sets M<sub>i</sub> is the set whose elements consist of the elements that occur in every single set M<sub>i</sub>.
  594. * <br>Expression: <code>∩ M = S</code>
  595. * <br>Example: <code>∩ {M_1, M_2, M_3} = S</code>
  596. * <br>Example: <code>∩ {A, B, C} = S</code>
  597. * <br>Example: <code>∩ {{0,1,2,4}, {1,2,9}, {0,1,2}} = {1,2}</code>
  598. * @example
  599. * const A = Set.from(0, 1, 2, 4)
  600. * const B = Set.from(1, 2, 9)
  601. * const C = Set.from(0, 1, 2)
  602. * Set.intersection(A, B, C) // Set { 1, 2 }
  603. * const M = [A, B, C]
  604. * Set.intersection(...M) // Set { 1, 2 }
  605. * @name Set.intersection
  606. * @function
  607. * @param args {...Set}- an arbitrary list of Set instances
  608. * @throws Throws an error if any of the arguments is not a Set instance.
  609. * @returns {Set} a Set instance with the shared elements of the given args.
  610. * @see https://en.wikipedia.org/wiki/Intersection_(set_theory)#Arbitrary_intersections
  611. */
  612. function intersectionArbitrary (...args) {
  613. checkSets(args)
  614. if (!args || args.length === 0) {
  615. throw new Error('The intersection operator currently does not support 0 arguments.')
  616. }
  617. const set3 = new Set()
  618. const minimumSet = args.reduce((prev, curr) => {
  619. return (prev.size < curr.size) ? prev : curr
  620. }, args[0])
  621. for (const value of minimumSet) {
  622. if (args.every(compare => compare.has(value))) {
  623. set3.add(value)
  624. }
  625. }
  626. return set3
  627. }
  628. /**
  629. * Creates the set intersection of two sets.
  630. * The intersection S of sets A and B is the set whose elements consist of the elements that occur in both A and B.
  631. * <br>Expression: <code>A ∩ B = S</code>
  632. * <br>Example: <code>{0,1,2,4} ∩ {1,2,9} = {1,2}</code>
  633. * @example
  634. * const A = Set.from(0, 1, 2, 4)
  635. * const B = Set.from(1, 2, 9)
  636. * A.intersect(B) // Set { 1, 2 }
  637. * @name Set.prototype.intersect
  638. * @function
  639. * @param args {set} - the other set to intersect with.
  640. * @throws Throws an error if there is not exactly one argument.
  641. * @throws Throws an error if the argument is not a Set instance.
  642. * @returns {Set} a Set instance with the shared elements of this set and the other set.
  643. * @see https://en.wikipedia.org/wiki/Intersection_(set_theory)#Definition
  644. */
  645. scope.Set.prototype.intersect = arbitraryToBinary(scope.Set.intersection)
  646. scope.Set.difference =
  647. /**
  648. * Computes the set difference of two sets (subtracts B from A): <code>C = A \ B</code>. This is also known as the "relative complement".
  649. *
  650. * @name Set.difference
  651. * @function
  652. * @throws Throws an error if any of the arguments is not a Set instance.
  653. * @param set1 - A the set to be subtracted from
  654. * @param set2 - B the set whose elements will be subtracted from A
  655. * @returns {Set|*} A new Set with all elements of A minus the elements of B
  656. */
  657. function difference (set1, set2) {
  658. checkSet(set1)
  659. checkSet(set2)
  660. const set3 = new Set([])
  661. set1.forEach(value => {
  662. if (!set2.has(value)) {
  663. set3.add(value)
  664. }
  665. })
  666. return set3
  667. }
  668. scope.Set.complement =
  669. /**
  670. * Computes the complement of set B where U is the universe: <code>C = U \ B</code>. This is also known as the "absolute complement".
  671. *
  672. * @name Set.complement
  673. * @function
  674. * @throws Throws an error if any of the arguments is not a Set instance.
  675. * @throws Throws an error if any element in B does not occur in U.
  676. * @param set1 - U the set to be subtracted from
  677. * @param set2 - B the set whose elements will be subtracted from A
  678. * @returns {Set|*} A new Set with all elements of U minus the elements of B
  679. */
  680. function complement (set1, set2) {
  681. checkSet(set1)
  682. checkSet(set2)
  683. if (!set1.isSupersetOf(set2)) {
  684. throw new Error('[set2] has an element which is not in the universe [set1].')
  685. }
  686. return Set.difference(set1, set2)
  687. }
  688. /**
  689. *
  690. * @private
  691. */
  692. function symDiff (set1, set2) {
  693. const set3 = new Set()
  694. function addToSet (source, compare, target) {
  695. source.forEach(value => {
  696. if (!compare.has(value)) {
  697. target.add(value)
  698. }
  699. })
  700. }
  701. addToSet(set1, set2, set3)
  702. addToSet(set2, set1, set3)
  703. return set3
  704. }
  705. scope.Set.symDiff =
  706. /**
  707. * Creates the symmetric difference (disjunctive union) of an arbitrary number (2 .. n) of sets.
  708. * The symmetric difference of two sets A and B is a set, that contains only those elements,
  709. * which are in either of the sets and not in their intersection.
  710. * The symmetric difference is commutative and associative, which is why arbitrary number of sets can be used as input
  711. * for a sequencial-computed symmetric difference.
  712. * <br>
  713. * Expression: <code>C = A Δ B</code>
  714. *
  715. * @function
  716. * @name Set.symDiff
  717. * @param args {...Set}- An arbitrary amount of Set instances
  718. * @example
  719. * const a = Set.from(1,2,3)
  720. * const b = Set.from(3,4)
  721. * Set.symDiff(a, b) // Set { 1, 2, 4 }
  722. * @throws Throws an error if any of the given arguments is not a set instance.
  723. * @returns {Set} Returns a new Set, that contains only elements.
  724. * @see https://en.wikipedia.org/wiki/Symmetric_difference
  725. */
  726. function symmetricDifference (...args) {
  727. args.forEach(arg => checkSet(arg))
  728. if (args.length === 2) {
  729. return symDiff(...args)
  730. }
  731. let set3 = symDiff(args.shift(), args.shift())
  732. while (args.length > 0) {
  733. set3 = symDiff(set3, args.shift())
  734. }
  735. return set3
  736. }
  737. scope.Set.cartesian =
  738. /**
  739. * Creates the cartesian product of two given sets.
  740. * The cartesian product of two sets A and B is the set of all ordered pairs (a, b) where a ∈ A and b ∈ B.
  741. * <br>
  742. * Expression: <code>C = A x B = { (a, b) | a ∈ A and b ∈ B}</code>
  743. * <br>
  744. * Note, that <code>A x B ≠ B x A</code> (not commutative)
  745. * @function
  746. * @name Set.cartesian
  747. * @param set1 {Set} - A set instance
  748. * @param set2 {Set} - A set instance
  749. * @example
  750. * const a = Set.from(1,2)
  751. * const b = Set.from(3,4)
  752. * Set.cartesian(a, b) // Set { [1, 3], [1, 4], [2, 3], [2, 4] }
  753. * Set.cartesian(b, a) // Set { [3, 1], [3, 2], [4, 1], [4, 2] }
  754. * @throws Throws an error unless both arguments are set instances.
  755. * @return {Set} a new set instance, that contains the ordered element pairs.
  756. * @see https://en.wikipedia.org/wiki/Cartesian_product
  757. */
  758. function cartesianProduct (set1, set2) {
  759. checkSet(set1)
  760. checkSet(set2)
  761. const set3 = new Set()
  762. set1.forEach(value1 => set2.forEach(value2 => set3.add([value1, value2])))
  763. return set3
  764. }
  765. /**
  766. * https://en.wikipedia.org/wiki/Power_set
  767. * @private
  768. */
  769. function subsets (S, output = new Set()) {
  770. checkSet(S)
  771. if (S.size === 0) {
  772. return Set.from(S)
  773. }
  774. const it = S.values()
  775. let result = it.next()
  776. while (!result.done) {
  777. const e = result.value
  778. const eSet = Set.from(e)
  779. // get difference between first element and the rest
  780. const diff = Set.difference(S, eSet)
  781. output.add(diff)
  782. // recursion: get subsets for the difference, too
  783. const subs = subsets(diff)
  784. subs.forEach(entry => output.add(entry))
  785. result = it.next()
  786. }
  787. return output
  788. }
  789. scope.Set.power =
  790. /**
  791. * Creates the powerset of a given set instance by using a recursive algorithm (see <a href="https://en.wikipedia.org/wiki/Power_set">Wikipedia</a>, section Algorithms).
  792. * The powerset of a set contains all possible subsets of the set, plus itself and the empty set.
  793. * <br>
  794. * <strong>Attention:</strong> This method grows exponentially with the size of the given set.
  795. * @name Set.power
  796. * @function
  797. * @param set {Set} - A Set instance.
  798. * @throws
  799. * Throws an error if the given set is not a set instance.
  800. * @returns {Set} a new set instance with all subsets of the given set, plus the given set itself and the empty set.
  801. * @see https://en.wikipedia.org/wiki/Power_set
  802. */
  803. function powerSet (set) {
  804. checkSet(set)
  805. const subs = subsets(set)
  806. subs.add(new Set())
  807. subs.add(set)
  808. set.forEach(value => subs.add(Set.from(value)))
  809. return subs
  810. }
  811. /** @private **/
  812. const mergeRulesAny = (strict, rules) => {
  813. const targetFn = strict
  814. ? rules.every
  815. : rules.some
  816. return value => {
  817. const passed = targetFn.call(rules, rule => rule.call(value))
  818. if (!passed) {
  819. throw new Error(`Value [${value}] does not match any rule of the ruleset.`)
  820. }
  821. return true
  822. }
  823. }
  824. scope.Set.mergeRules =
  825. /**
  826. * Merges two rules functions with a strict pass concept.
  827. * The resulting function requires the given element to pass at least one of the given functions (logical OR).
  828. * @function
  829. * @name Set.mergeRules
  830. * @throws Throws an error if any of the given parameters is not a Function
  831. * @param rules {...Function} - An arbitrary amount of (rules-) functions. See {@link Set.prototype.rules} for requirements of a rules function.
  832. * @returns {function(*=): boolean} The resulting rules function that can be attached to a set instance.
  833. * @see Set.prototype.rules
  834. *
  835. */
  836. function mergeRules (...rules) {
  837. checkRules(rules)
  838. return mergeRulesAny(false, rules)
  839. }
  840. scope.Set.mergeRulesStrict =
  841. /**
  842. * Merges two rules functions with a strict pass concept.
  843. * The resulting function requires the given element to pass all of the given functions (logical AND).
  844. * Thus, if the element fails one, it fails all.
  845. * <strong>Attention:</strong> If passed rules are mutually exclusive, none given element will pass the test in any circumstance.
  846. * @function
  847. * @name Set.mergeRulesStrict
  848. * @throws Throws an error if any of the given parameters is not a Function
  849. * @param rules {...Function} - An arbitrary amount of (rules-) functions. See {@link Set.prototype.rules} for requirements of a rules function.
  850. * @returns {function(*=): boolean} The resulting rules function that can be attached to a set instance.
  851. * @see Set.prototype.rules
  852. */
  853. function mergeRulesStrict (...rules) {
  854. checkRules(rules)
  855. return mergeRulesAny(true, rules)
  856. }
  857. /**
  858. * Flag to indicate the presence of this polyfill
  859. * @type {boolean}
  860. * @private
  861. */
  862. scope.Set.__isExtended__ = true