Source: collections/Stack.js

  1. const { Collection, CollectionEvent } = require('./Collection')
  2. , { Resolve } = require('../tools/Resolve')
  3. , { EqualityComparer } = require('./EqualityComparer')
  4. , { Observable, fromEvent } = require('rxjs')
  5. , symbolStackPush = Symbol('stackPush')
  6. , symbolStackPop = Symbol('stackPop')
  7. , symbolStackPopBottom = Symbol('stackPopBottom');
  8. /**
  9. * @template T
  10. * @author Sebastian Hönel <development@hoenel.net>
  11. */
  12. class Stack extends Collection {
  13. /**
  14. * Creates a new, empty Stack<T>.
  15. *
  16. * @param {EqualityComparer<T>} [eqComparer] Optional. Defaults To EqualityComparer<T>.default.
  17. */
  18. constructor(eqComparer = EqualityComparer.default) {
  19. super(eqComparer);
  20. /** @type {Observable<T>} */
  21. this.observablePush = Object.freeze(fromEvent(this, symbolStackPush));
  22. /** @type {Observable<T>} */
  23. this.observablePop = Object.freeze(fromEvent(this, symbolStackPop));
  24. /** @type {Observable<T>} */
  25. this.observablePopBottom = Object.freeze(fromEvent(this, symbolStackPopBottom));
  26. };
  27. /**
  28. * Push an item to the top of the Stack.
  29. *
  30. * @param {T} item
  31. * @returns {this}
  32. */
  33. push(item) {
  34. this._items.push(item);
  35. this.emit(symbolStackPush, new CollectionEvent(item));
  36. return this;
  37. };
  38. /**
  39. * @throws {Error} If the Stack is empty.
  40. * @returns {T} The item at the top of the Stack (the item
  41. * inserted last).
  42. */
  43. pop() {
  44. if (this.isEmpty) {
  45. throw new Error('The Stack is empty.');
  46. }
  47. const item = this._items.pop();
  48. this.emit(symbolStackPop, new CollectionEvent(item));
  49. return item;
  50. };
  51. /**
  52. * @throws {Error} If the Stack is empty.
  53. * @returns {T} The item at the bottom of the Stack (the item
  54. * inserted first).
  55. */
  56. popBottom() {
  57. if (this.isEmpty) {
  58. throw new Error('The Stack is empty.');
  59. }
  60. const item = this._items.shift();
  61. this.emit(symbolStackPopBottom, new CollectionEvent(item));
  62. return item;
  63. };
  64. /**
  65. * @throws {Error} If the Stack is empty.
  66. * @returns {T} The item on top of the Stack without popping it.
  67. */
  68. peek() {
  69. if (this.isEmpty) {
  70. throw new Error('The Stack is empty.');
  71. }
  72. return this._items[this.size - 1];
  73. };
  74. /**
  75. * @throws {Error} If the Stack is empty.
  76. * @returns {T} The item at the bottom of the Stack without popping it.
  77. */
  78. peekBottom() {
  79. if (this.isEmpty) {
  80. throw new Error('The Stack is empty.');
  81. }
  82. return this._items[0];
  83. };
  84. };
  85. class ConstrainedStack extends Stack {
  86. /**
  87. * Creates a new, empty Stack<T>.
  88. *
  89. * @param {Number} [maxSize] Optional. Defaults to Number.MAX_SAFE_INTEGER. Use
  90. * this parameter to limit the maximum amount of elements this Stack can hold.
  91. * When the limit is reached and items are being further pushed, the
  92. * ConstrainedStack will pop items from the BOTTOM. I.e., when pushing a new item
  93. * to the top of the Stack when it is full, will discard one item at its bottom.
  94. * This parameter must be a positive integer larger than zero.
  95. * @param {EqualityComparer<T>} [eqComparer] Optional. Defaults To
  96. * EqualityComparer<T>.default.
  97. */
  98. constructor(maxSize = Number.MAX_SAFE_INTEGER, eqComparer = EqualityComparer.default) {
  99. super(eqComparer);
  100. this._maxSize = 1;
  101. this.maxSize = maxSize;
  102. };
  103. /**
  104. * @type {Number}
  105. */
  106. get maxSize() {
  107. return this._maxSize;
  108. };
  109. /**
  110. * Sets the maximum size of this ConstrainedStack. If currently there are more
  111. * items, the stack will pop items from the bottom until the number of items
  112. * does not longer exceed the new maximum size. The items will be popped from
  113. * the BOTTOM.
  114. *
  115. * @param {Number} value The new value for maxSize. Must be an integer equal
  116. * to or larger than 1.
  117. * @throws {Error} If parameter value is not a number or less than one (1).
  118. * @type {void}
  119. */
  120. set maxSize(value) {
  121. if (!Resolve.isTypeOf(value, Number) || !Number.isInteger(value)) {
  122. throw new Error(`The value given for maxSize is not a number.`);
  123. }
  124. if (value < 1) {
  125. throw new Error(`The value given is less than 1: ${value}`);
  126. }
  127. this._maxSize = value;
  128. this._truncate();
  129. };
  130. /**
  131. * @returns {this}
  132. */
  133. _truncate() {
  134. let excess = this.size - this.maxSize;
  135. while (excess > 0) {
  136. // Triggers/emits symbol for poppong items from bottom.
  137. this.popBottom();
  138. excess--;
  139. }
  140. return this;
  141. };
  142. /**
  143. * Push an item to the top of the Stack. If, after pushing this item,
  144. * the Stack is larger than its specified maximum size, it will discard
  145. * an item from the BOTTOM.
  146. *
  147. * @override
  148. * @inheritdoc
  149. * @param {T} item
  150. * @returns {this}
  151. */
  152. push(item) {
  153. super.push(item);
  154. return this._truncate();
  155. };
  156. };
  157. module.exports = Object.freeze({
  158. Stack,
  159. ConstrainedStack,
  160. symbolStackPop,
  161. symbolStackPush,
  162. symbolStackPopBottom
  163. });