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;
 |