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.
		
		
		
		
		
			
		
			
				
					
					
						
							409 lines
						
					
					
						
							7.9 KiB
						
					
					
				
			
		
		
	
	
							409 lines
						
					
					
						
							7.9 KiB
						
					
					
				/**
 | 
						|
 * Mnemonist MultiMap
 | 
						|
 * ===================
 | 
						|
 *
 | 
						|
 * Implementation of a MultiMap with custom container.
 | 
						|
 */
 | 
						|
var Iterator = require('obliterator/iterator'),
 | 
						|
    forEach = require('obliterator/foreach');
 | 
						|
 | 
						|
/**
 | 
						|
 * MultiMap.
 | 
						|
 *
 | 
						|
 * @constructor
 | 
						|
 */
 | 
						|
function MultiMap(Container) {
 | 
						|
 | 
						|
  this.Container = Container || Array;
 | 
						|
  this.items = new Map();
 | 
						|
  this.clear();
 | 
						|
 | 
						|
  Object.defineProperty(this.items, 'constructor', {
 | 
						|
    value: MultiMap,
 | 
						|
    enumerable: false
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to clear the structure.
 | 
						|
 *
 | 
						|
 * @return {undefined}
 | 
						|
 */
 | 
						|
MultiMap.prototype.clear = function() {
 | 
						|
 | 
						|
  // Properties
 | 
						|
  this.size = 0;
 | 
						|
  this.dimension = 0;
 | 
						|
  this.items.clear();
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to set a value.
 | 
						|
 *
 | 
						|
 * @param  {any}      key   - Key.
 | 
						|
 * @param  {any}      value - Value to add.
 | 
						|
 * @return {MultiMap}
 | 
						|
 */
 | 
						|
MultiMap.prototype.set = function(key, value) {
 | 
						|
  var container = this.items.get(key),
 | 
						|
      sizeBefore;
 | 
						|
 | 
						|
  if (!container) {
 | 
						|
    this.dimension++;
 | 
						|
    container = new this.Container();
 | 
						|
    this.items.set(key, container);
 | 
						|
  }
 | 
						|
 | 
						|
  if (this.Container === Set) {
 | 
						|
    sizeBefore = container.size;
 | 
						|
    container.add(value);
 | 
						|
 | 
						|
    if (sizeBefore < container.size)
 | 
						|
      this.size++;
 | 
						|
  }
 | 
						|
  else {
 | 
						|
    container.push(value);
 | 
						|
    this.size++;
 | 
						|
  }
 | 
						|
 | 
						|
  return this;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to delete the given key.
 | 
						|
 *
 | 
						|
 * @param  {any}     key - Key to delete.
 | 
						|
 * @return {boolean}
 | 
						|
 */
 | 
						|
MultiMap.prototype.delete = function(key) {
 | 
						|
  var container = this.items.get(key);
 | 
						|
 | 
						|
  if (!container)
 | 
						|
    return false;
 | 
						|
 | 
						|
  this.size -= (this.Container === Set ? container.size : container.length);
 | 
						|
  this.dimension--;
 | 
						|
  this.items.delete(key);
 | 
						|
 | 
						|
  return true;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to delete the remove an item in the container stored at the
 | 
						|
 * given key.
 | 
						|
 *
 | 
						|
 * @param  {any}     key - Key to delete.
 | 
						|
 * @return {boolean}
 | 
						|
 */
 | 
						|
MultiMap.prototype.remove = function(key, value) {
 | 
						|
  var container = this.items.get(key),
 | 
						|
      wasDeleted,
 | 
						|
      index;
 | 
						|
 | 
						|
  if (!container)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (this.Container === Set) {
 | 
						|
    wasDeleted = container.delete(value);
 | 
						|
 | 
						|
    if (wasDeleted)
 | 
						|
      this.size--;
 | 
						|
 | 
						|
    if (container.size === 0) {
 | 
						|
      this.items.delete(key);
 | 
						|
      this.dimension--;
 | 
						|
    }
 | 
						|
 | 
						|
    return wasDeleted;
 | 
						|
  }
 | 
						|
  else {
 | 
						|
    index = container.indexOf(value);
 | 
						|
 | 
						|
    if (index === -1)
 | 
						|
      return false;
 | 
						|
 | 
						|
    this.size--;
 | 
						|
 | 
						|
    if (container.length === 1) {
 | 
						|
      this.items.delete(key);
 | 
						|
      this.dimension--;
 | 
						|
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
 | 
						|
    container.splice(index, 1);
 | 
						|
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to return whether the given keys exists in the map.
 | 
						|
 *
 | 
						|
 * @param  {any}     key - Key to check.
 | 
						|
 * @return {boolean}
 | 
						|
 */
 | 
						|
MultiMap.prototype.has = function(key) {
 | 
						|
  return this.items.has(key);
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to return the container stored at the given key or `undefined`.
 | 
						|
 *
 | 
						|
 * @param  {any}     key - Key to get.
 | 
						|
 * @return {boolean}
 | 
						|
 */
 | 
						|
MultiMap.prototype.get = function(key) {
 | 
						|
  return this.items.get(key);
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to return the multiplicity of the given key, meaning the number
 | 
						|
 * of times it is set, or, more trivially, the size of the attached container.
 | 
						|
 *
 | 
						|
 * @param  {any}     key - Key to check.
 | 
						|
 * @return {number}
 | 
						|
 */
 | 
						|
MultiMap.prototype.multiplicity = function(key) {
 | 
						|
  var container = this.items.get(key);
 | 
						|
 | 
						|
  if (typeof container === 'undefined')
 | 
						|
    return 0;
 | 
						|
 | 
						|
  return this.Container === Set ? container.size : container.length;
 | 
						|
};
 | 
						|
MultiMap.prototype.count = MultiMap.prototype.multiplicity;
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to iterate over each of the key/value pairs.
 | 
						|
 *
 | 
						|
 * @param  {function}  callback - Function to call for each item.
 | 
						|
 * @param  {object}    scope    - Optional scope.
 | 
						|
 * @return {undefined}
 | 
						|
 */
 | 
						|
MultiMap.prototype.forEach = function(callback, scope) {
 | 
						|
  scope = arguments.length > 1 ? scope : this;
 | 
						|
 | 
						|
  // Inner iteration function is created here to avoid creating it in the loop
 | 
						|
  var key;
 | 
						|
  function inner(value) {
 | 
						|
    callback.call(scope, value, key);
 | 
						|
  }
 | 
						|
 | 
						|
  this.items.forEach(function(container, k) {
 | 
						|
    key = k;
 | 
						|
    container.forEach(inner);
 | 
						|
  });
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method used to iterate over each of the associations.
 | 
						|
 *
 | 
						|
 * @param  {function}  callback - Function to call for each item.
 | 
						|
 * @param  {object}    scope    - Optional scope.
 | 
						|
 * @return {undefined}
 | 
						|
 */
 | 
						|
MultiMap.prototype.forEachAssociation = function(callback, scope) {
 | 
						|
  scope = arguments.length > 1 ? scope : this;
 | 
						|
 | 
						|
  this.items.forEach(callback, scope);
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method returning an iterator over the map's keys.
 | 
						|
 *
 | 
						|
 * @return {Iterator}
 | 
						|
 */
 | 
						|
MultiMap.prototype.keys = function() {
 | 
						|
  return this.items.keys();
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method returning an iterator over the map's keys.
 | 
						|
 *
 | 
						|
 * @return {Iterator}
 | 
						|
 */
 | 
						|
MultiMap.prototype.values = function() {
 | 
						|
  var iterator = this.items.values(),
 | 
						|
      inContainer = false,
 | 
						|
      countainer,
 | 
						|
      step,
 | 
						|
      i,
 | 
						|
      l;
 | 
						|
 | 
						|
  if (this.Container === Set)
 | 
						|
    return new Iterator(function next() {
 | 
						|
      if (!inContainer) {
 | 
						|
        step = iterator.next();
 | 
						|
 | 
						|
        if (step.done)
 | 
						|
          return {done: true};
 | 
						|
 | 
						|
        inContainer = true;
 | 
						|
        countainer = step.value.values();
 | 
						|
      }
 | 
						|
 | 
						|
      step = countainer.next();
 | 
						|
 | 
						|
      if (step.done) {
 | 
						|
        inContainer = false;
 | 
						|
        return next();
 | 
						|
      }
 | 
						|
 | 
						|
      return {
 | 
						|
        done: false,
 | 
						|
        value: step.value
 | 
						|
      };
 | 
						|
    });
 | 
						|
 | 
						|
  return new Iterator(function next() {
 | 
						|
    if (!inContainer) {
 | 
						|
      step = iterator.next();
 | 
						|
 | 
						|
      if (step.done)
 | 
						|
        return {done: true};
 | 
						|
 | 
						|
      inContainer = true;
 | 
						|
      countainer = step.value;
 | 
						|
      i = 0;
 | 
						|
      l = countainer.length;
 | 
						|
    }
 | 
						|
 | 
						|
    if (i >= l) {
 | 
						|
      inContainer = false;
 | 
						|
      return next();
 | 
						|
    }
 | 
						|
 | 
						|
    return {
 | 
						|
      done: false,
 | 
						|
      value: countainer[i++]
 | 
						|
    };
 | 
						|
  });
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method returning an iterator over the map's entries.
 | 
						|
 *
 | 
						|
 * @return {Iterator}
 | 
						|
 */
 | 
						|
MultiMap.prototype.entries = function() {
 | 
						|
  var iterator = this.items.entries(),
 | 
						|
      inContainer = false,
 | 
						|
      countainer,
 | 
						|
      step,
 | 
						|
      key,
 | 
						|
      i,
 | 
						|
      l;
 | 
						|
 | 
						|
  if (this.Container === Set)
 | 
						|
    return new Iterator(function next() {
 | 
						|
      if (!inContainer) {
 | 
						|
        step = iterator.next();
 | 
						|
 | 
						|
        if (step.done)
 | 
						|
          return {done: true};
 | 
						|
 | 
						|
        inContainer = true;
 | 
						|
        key = step.value[0];
 | 
						|
        countainer = step.value[1].values();
 | 
						|
      }
 | 
						|
 | 
						|
      step = countainer.next();
 | 
						|
 | 
						|
      if (step.done) {
 | 
						|
        inContainer = false;
 | 
						|
        return next();
 | 
						|
      }
 | 
						|
 | 
						|
      return {
 | 
						|
        done: false,
 | 
						|
        value: [key, step.value]
 | 
						|
      };
 | 
						|
    });
 | 
						|
 | 
						|
  return new Iterator(function next() {
 | 
						|
    if (!inContainer) {
 | 
						|
      step = iterator.next();
 | 
						|
 | 
						|
      if (step.done)
 | 
						|
        return {done: true};
 | 
						|
 | 
						|
      inContainer = true;
 | 
						|
      key = step.value[0];
 | 
						|
      countainer = step.value[1];
 | 
						|
      i = 0;
 | 
						|
      l = countainer.length;
 | 
						|
    }
 | 
						|
 | 
						|
    if (i >= l) {
 | 
						|
      inContainer = false;
 | 
						|
      return next();
 | 
						|
    }
 | 
						|
 | 
						|
    return {
 | 
						|
      done: false,
 | 
						|
      value: [key, countainer[i++]]
 | 
						|
    };
 | 
						|
  });
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method returning an iterator over the map's containers.
 | 
						|
 *
 | 
						|
 * @return {Iterator}
 | 
						|
 */
 | 
						|
MultiMap.prototype.containers = function() {
 | 
						|
  return this.items.values();
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Method returning an iterator over the map's associations.
 | 
						|
 *
 | 
						|
 * @return {Iterator}
 | 
						|
 */
 | 
						|
MultiMap.prototype.associations = function() {
 | 
						|
  return this.items.entries();
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Attaching the #.entries method to Symbol.iterator if possible.
 | 
						|
 */
 | 
						|
if (typeof Symbol !== 'undefined')
 | 
						|
  MultiMap.prototype[Symbol.iterator] = MultiMap.prototype.entries;
 | 
						|
 | 
						|
/**
 | 
						|
 * Convenience known methods.
 | 
						|
 */
 | 
						|
MultiMap.prototype.inspect = function() {
 | 
						|
  return this.items;
 | 
						|
};
 | 
						|
 | 
						|
if (typeof Symbol !== 'undefined')
 | 
						|
  MultiMap.prototype[Symbol.for('nodejs.util.inspect.custom')] = MultiMap.prototype.inspect;
 | 
						|
MultiMap.prototype.toJSON = function() {
 | 
						|
  return this.items;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Static @.from function taking an arbitrary iterable & converting it into
 | 
						|
 * a structure.
 | 
						|
 *
 | 
						|
 * @param  {Iterable} iterable  - Target iterable.
 | 
						|
 * @param  {Class}    Container - Container.
 | 
						|
 * @return {MultiMap}
 | 
						|
 */
 | 
						|
MultiMap.from = function(iterable, Container) {
 | 
						|
  var map = new MultiMap(Container);
 | 
						|
 | 
						|
  forEach(iterable, function(value, key) {
 | 
						|
    map.set(key, value);
 | 
						|
  });
 | 
						|
 | 
						|
  return map;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Exporting.
 | 
						|
 */
 | 
						|
module.exports = MultiMap;
 |