You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							446 lines
						
					
					
						
							8.9 KiB
						
					
					
				
			
		
		
	
	
							446 lines
						
					
					
						
							8.9 KiB
						
					
					
				| /**
 | |
|  * Mnemonist MultiSet
 | |
|  * ====================
 | |
|  *
 | |
|  * JavaScript implementation of a MultiSet.
 | |
|  */
 | |
| var Iterator = require('obliterator/iterator'),
 | |
|     forEach = require('obliterator/foreach'),
 | |
|     FixedReverseHeap = require('./fixed-reverse-heap.js');
 | |
| 
 | |
| /**
 | |
|  * Helpers.
 | |
|  */
 | |
| var MULTISET_ITEM_COMPARATOR = function(a, b) {
 | |
|   if (a[1] > b[1])
 | |
|     return -1;
 | |
|   if (a[1] < b[1])
 | |
|     return 1;
 | |
| 
 | |
|   return 0;
 | |
| };
 | |
| 
 | |
| // TODO: helper functions: union, intersection, sum, difference, subtract
 | |
| 
 | |
| /**
 | |
|  * MultiSet.
 | |
|  *
 | |
|  * @constructor
 | |
|  */
 | |
| function MultiSet() {
 | |
|   this.items = new Map();
 | |
| 
 | |
|   Object.defineProperty(this.items, 'constructor', {
 | |
|     value: MultiSet,
 | |
|     enumerable: false
 | |
|   });
 | |
| 
 | |
|   this.clear();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Method used to clear the structure.
 | |
|  *
 | |
|  * @return {undefined}
 | |
|  */
 | |
| MultiSet.prototype.clear = function() {
 | |
| 
 | |
|   // Properties
 | |
|   this.size = 0;
 | |
|   this.dimension = 0;
 | |
|   this.items.clear();
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to add an item to the set.
 | |
|  *
 | |
|  * @param  {any}    item  - Item to add.
 | |
|  * @param  {number} count - Optional count.
 | |
|  * @return {MultiSet}
 | |
|  */
 | |
| MultiSet.prototype.add = function(item, count) {
 | |
|   if (count === 0)
 | |
|     return this;
 | |
| 
 | |
|   if (count < 0)
 | |
|     return this.remove(item, -count);
 | |
| 
 | |
|   count = count || 1;
 | |
| 
 | |
|   if (typeof count !== 'number')
 | |
|     throw new Error('mnemonist/multi-set.add: given count should be a number.');
 | |
| 
 | |
|   this.size += count;
 | |
| 
 | |
|   const currentCount = this.items.get(item);
 | |
| 
 | |
|   if (currentCount === undefined)
 | |
|     this.dimension++;
 | |
|   else
 | |
|     count += currentCount;
 | |
| 
 | |
|   this.items.set(item, count);
 | |
| 
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to set the multiplicity of an item in the set.
 | |
|  *
 | |
|  * @param  {any}    item  - Target item.
 | |
|  * @param  {number} count - Desired multiplicity.
 | |
|  * @return {MultiSet}
 | |
|  */
 | |
| MultiSet.prototype.set = function(item, count) {
 | |
|   var currentCount;
 | |
| 
 | |
|   if (typeof count !== 'number')
 | |
|     throw new Error('mnemonist/multi-set.set: given count should be a number.');
 | |
| 
 | |
|   // Setting an item to 0 or to a negative number means deleting it from the set
 | |
|   if (count <= 0) {
 | |
|     currentCount = this.items.get(item);
 | |
| 
 | |
|     if (typeof currentCount !== 'undefined') {
 | |
|       this.size -= currentCount;
 | |
|       this.dimension--;
 | |
|     }
 | |
| 
 | |
|     this.items.delete(item);
 | |
|     return this;
 | |
|   }
 | |
| 
 | |
|   count = count || 1;
 | |
| 
 | |
|   currentCount = this.items.get(item);
 | |
| 
 | |
|   if (typeof currentCount === 'number') {
 | |
|     this.items.set(item, currentCount + count);
 | |
|   }
 | |
|   else {
 | |
|     this.dimension++;
 | |
|     this.items.set(item, count);
 | |
|   }
 | |
| 
 | |
|   this.size += count;
 | |
| 
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to return whether the item exists in the set.
 | |
|  *
 | |
|  * @param  {any} item  - Item to check.
 | |
|  * @return {boolan}
 | |
|  */
 | |
| MultiSet.prototype.has = function(item) {
 | |
|   return this.items.has(item);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to delete an item from the set.
 | |
|  *
 | |
|  * @param  {any} item  - Item to delete.
 | |
|  * @return {boolan}
 | |
|  */
 | |
| MultiSet.prototype.delete = function(item) {
 | |
|   var count = this.items.get(item);
 | |
| 
 | |
|   if (count === 0)
 | |
|     return false;
 | |
| 
 | |
|   this.size -= count;
 | |
|   this.dimension--;
 | |
|   this.items.delete(item);
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to remove an item from the set.
 | |
|  *
 | |
|  * @param  {any} item  - Item to delete.
 | |
|  * @param  {number} count - Optional count.
 | |
|  * @return {undefined}
 | |
|  */
 | |
| MultiSet.prototype.remove = function(item, count) {
 | |
|   if (count === 0)
 | |
|     return;
 | |
| 
 | |
|   if (count < 0)
 | |
|     return this.add(item, -count);
 | |
| 
 | |
|   count = count || 1;
 | |
| 
 | |
|   if (typeof count !== 'number')
 | |
|     throw new Error('mnemonist/multi-set.remove: given count should be a number.');
 | |
| 
 | |
|   var currentCount = this.items.get(item);
 | |
| 
 | |
|   if (typeof currentCount === 'undefined') return;
 | |
| 
 | |
|   var newCount = Math.max(0, currentCount - count);
 | |
| 
 | |
|   if (newCount === 0) {
 | |
|     this.items.delete(item);
 | |
|     this.size -= currentCount;
 | |
|     this.dimension--;
 | |
|   }
 | |
|   else {
 | |
|     this.items.set(item, newCount);
 | |
|     this.size -= count;
 | |
|   }
 | |
| 
 | |
|   return;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to change a key into another one, merging counts if the target
 | |
|  * key already exists.
 | |
|  *
 | |
|  * @param  {any} a - From key.
 | |
|  * @param  {any} b - To key.
 | |
|  * @return {MultiSet}
 | |
|  */
 | |
| MultiSet.prototype.edit = function(a, b) {
 | |
|   var am = this.multiplicity(a);
 | |
| 
 | |
|   // If a does not exist in the set, we can stop right there
 | |
|   if (am === 0)
 | |
|     return;
 | |
| 
 | |
|   var bm = this.multiplicity(b);
 | |
| 
 | |
|   this.items.set(b, am + bm);
 | |
|   this.items.delete(a);
 | |
| 
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to return the multiplicity of the given item.
 | |
|  *
 | |
|  * @param  {any} item  - Item to get.
 | |
|  * @return {number}
 | |
|  */
 | |
| MultiSet.prototype.multiplicity = function(item) {
 | |
|   var count = this.items.get(item);
 | |
| 
 | |
|   if (typeof count === 'undefined')
 | |
|     return 0;
 | |
| 
 | |
|   return count;
 | |
| };
 | |
| MultiSet.prototype.get = MultiSet.prototype.multiplicity;
 | |
| MultiSet.prototype.count = MultiSet.prototype.multiplicity;
 | |
| 
 | |
| /**
 | |
|  * Method used to return the frequency of the given item in the set.
 | |
|  *
 | |
|  * @param  {any} item - Item to get.
 | |
|  * @return {number}
 | |
|  */
 | |
| MultiSet.prototype.frequency = function(item) {
 | |
|   if (this.size === 0)
 | |
|     return 0;
 | |
| 
 | |
|   var count = this.multiplicity(item);
 | |
| 
 | |
|   return count / this.size;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to return the n most common items from the set.
 | |
|  *
 | |
|  * @param  {number} n - Number of items to retrieve.
 | |
|  * @return {array}
 | |
|  */
 | |
| MultiSet.prototype.top = function(n) {
 | |
|   if (typeof n !== 'number' || n <= 0)
 | |
|     throw new Error('mnemonist/multi-set.top: n must be a number > 0.');
 | |
| 
 | |
|   var heap = new FixedReverseHeap(Array, MULTISET_ITEM_COMPARATOR, n);
 | |
| 
 | |
|   var iterator = this.items.entries(),
 | |
|       step;
 | |
| 
 | |
|   while ((step = iterator.next(), !step.done))
 | |
|     heap.push(step.value);
 | |
| 
 | |
|   return heap.consume();
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to iterate over the set's values.
 | |
|  *
 | |
|  * @param  {function}  callback - Function to call for each item.
 | |
|  * @param  {object}    scope    - Optional scope.
 | |
|  * @return {undefined}
 | |
|  */
 | |
| MultiSet.prototype.forEach = function(callback, scope) {
 | |
|   scope = arguments.length > 1 ? scope : this;
 | |
| 
 | |
|   var i;
 | |
| 
 | |
|   this.items.forEach(function(multiplicity, value) {
 | |
| 
 | |
|     for (i = 0; i < multiplicity; i++)
 | |
|       callback.call(scope, value, value);
 | |
|   });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method used to iterate over the set's multiplicities.
 | |
|  *
 | |
|  * @param  {function}  callback - Function to call for each multiplicity.
 | |
|  * @param  {object}    scope    - Optional scope.
 | |
|  * @return {undefined}
 | |
|  */
 | |
| MultiSet.prototype.forEachMultiplicity = function(callback, scope) {
 | |
|   scope = arguments.length > 1 ? scope : this;
 | |
| 
 | |
|   this.items.forEach(callback, scope);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method returning an iterator over the set's keys. I.e. its unique values,
 | |
|  * in a sense.
 | |
|  *
 | |
|  * @return {Iterator}
 | |
|  */
 | |
| MultiSet.prototype.keys = function() {
 | |
|   return this.items.keys();
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method returning an iterator over the set's values.
 | |
|  *
 | |
|  * @return {Iterator}
 | |
|  */
 | |
| MultiSet.prototype.values = function() {
 | |
|   var iterator = this.items.entries(),
 | |
|       inContainer = false,
 | |
|       step,
 | |
|       value,
 | |
|       multiplicity,
 | |
|       i;
 | |
| 
 | |
|   return new Iterator(function next() {
 | |
|     if (!inContainer) {
 | |
|       step = iterator.next();
 | |
| 
 | |
|       if (step.done)
 | |
|         return {done: true};
 | |
| 
 | |
|       inContainer = true;
 | |
|       value = step.value[0];
 | |
|       multiplicity = step.value[1];
 | |
|       i = 0;
 | |
|     }
 | |
| 
 | |
|     if (i >= multiplicity) {
 | |
|       inContainer = false;
 | |
|       return next();
 | |
|     }
 | |
| 
 | |
|     i++;
 | |
| 
 | |
|     return {
 | |
|       done: false,
 | |
|       value: value
 | |
|     };
 | |
|   });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Method returning an iterator over the set's multiplicities.
 | |
|  *
 | |
|  * @return {Iterator}
 | |
|  */
 | |
| MultiSet.prototype.multiplicities = function() {
 | |
|   return this.items.entries();
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Attaching the #.entries method to Symbol.iterator if possible.
 | |
|  */
 | |
| if (typeof Symbol !== 'undefined')
 | |
|   MultiSet.prototype[Symbol.iterator] = MultiSet.prototype.values;
 | |
| 
 | |
| /**
 | |
|  * Convenience known methods.
 | |
|  */
 | |
| MultiSet.prototype.inspect = function() {
 | |
|   return this.items;
 | |
| };
 | |
| 
 | |
| if (typeof Symbol !== 'undefined')
 | |
|   MultiSet.prototype[Symbol.for('nodejs.util.inspect.custom')] = MultiSet.prototype.inspect;
 | |
| MultiSet.prototype.toJSON = function() {
 | |
|   return this.items;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Static @.from function taking an arbitrary iterable & converting it into
 | |
|  * a structure.
 | |
|  *
 | |
|  * @param  {Iterable} iterable - Target iterable.
 | |
|  * @return {MultiSet}
 | |
|  */
 | |
| MultiSet.from = function(iterable) {
 | |
|   var set = new MultiSet();
 | |
| 
 | |
|   forEach(iterable, function(value) {
 | |
|     set.add(value);
 | |
|   });
 | |
| 
 | |
|   return set;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Function returning whether the multiset A is a subset of the multiset B.
 | |
|  *
 | |
|  * @param  {MultiSet} A - First set.
 | |
|  * @param  {MultiSet} B - Second set.
 | |
|  * @return {boolean}
 | |
|  */
 | |
| MultiSet.isSubset = function(A, B) {
 | |
|   var iterator = A.multiplicities(),
 | |
|       step,
 | |
|       key,
 | |
|       mA;
 | |
| 
 | |
|   // Shortcuts
 | |
|   if (A === B)
 | |
|     return true;
 | |
| 
 | |
|   if (A.dimension > B.dimension)
 | |
|     return false;
 | |
| 
 | |
|   while ((step = iterator.next(), !step.done)) {
 | |
|     key = step.value[0];
 | |
|     mA = step.value[1];
 | |
| 
 | |
|     if (B.multiplicity(key) < mA)
 | |
|       return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Function returning whether the multiset A is a superset of the multiset B.
 | |
|  *
 | |
|  * @param  {MultiSet} A - First set.
 | |
|  * @param  {MultiSet} B - Second set.
 | |
|  * @return {boolean}
 | |
|  */
 | |
| MultiSet.isSuperset = function(A, B) {
 | |
|   return MultiSet.isSubset(B, A);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Exporting.
 | |
|  */
 | |
| module.exports = MultiSet;
 |