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.
		
		
		
		
		
			
		
			
				
					357 lines
				
				6.6 KiB
			
		
		
			
		
	
	
					357 lines
				
				6.6 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Mnemonist Set
							 | 
						||
| 
								 | 
							
								 * ==============
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * Useful function related to sets such as union, intersection and so on...
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// TODO: optimize versions for less variadicities
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Variadic function computing the intersection of multiple sets.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {...Set} sets - Sets to intersect.
							 | 
						||
| 
								 | 
							
								 * @return {Set}         - The intesection.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.intersection = function() {
							 | 
						||
| 
								 | 
							
								  if (arguments.length < 2)
							 | 
						||
| 
								 | 
							
								    throw new Error('mnemonist/Set.intersection: needs at least two arguments.');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var I = new Set();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // First we need to find the smallest set
							 | 
						||
| 
								 | 
							
								  var smallestSize = Infinity,
							 | 
						||
| 
								 | 
							
								      smallestSet = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var s, i, l = arguments.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for (i = 0; i < l; i++) {
							 | 
						||
| 
								 | 
							
								    s = arguments[i];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // If one of the set has no items, we can stop right there
							 | 
						||
| 
								 | 
							
								    if (s.size === 0)
							 | 
						||
| 
								 | 
							
								      return I;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (s.size < smallestSize) {
							 | 
						||
| 
								 | 
							
								      smallestSize = s.size;
							 | 
						||
| 
								 | 
							
								      smallestSet = s;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Now we need to intersect this set with the others
							 | 
						||
| 
								 | 
							
								  var iterator = smallestSet.values(),
							 | 
						||
| 
								 | 
							
								      step,
							 | 
						||
| 
								 | 
							
								      item,
							 | 
						||
| 
								 | 
							
								      add,
							 | 
						||
| 
								 | 
							
								      set;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // TODO: we can optimize by iterating each next time over the current intersection
							 | 
						||
| 
								 | 
							
								  // but this probably means more RAM to consume since we'll create n-1 sets rather than
							 | 
						||
| 
								 | 
							
								  // only the one.
							 | 
						||
| 
								 | 
							
								  while ((step = iterator.next(), !step.done)) {
							 | 
						||
| 
								 | 
							
								    item = step.value;
							 | 
						||
| 
								 | 
							
								    add = true;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for (i = 0; i < l; i++) {
							 | 
						||
| 
								 | 
							
								      set = arguments[i];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (set === smallestSet)
							 | 
						||
| 
								 | 
							
								        continue;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (!set.has(item)) {
							 | 
						||
| 
								 | 
							
								        add = false;
							 | 
						||
| 
								 | 
							
								        break;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (add)
							 | 
						||
| 
								 | 
							
								      I.add(item);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return I;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Variadic function computing the union of multiple sets.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {...Set} sets - Sets to unite.
							 | 
						||
| 
								 | 
							
								 * @return {Set}         - The union.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.union = function() {
							 | 
						||
| 
								 | 
							
								  if (arguments.length < 2)
							 | 
						||
| 
								 | 
							
								    throw new Error('mnemonist/Set.union: needs at least two arguments.');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var U = new Set();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var i, l = arguments.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var iterator,
							 | 
						||
| 
								 | 
							
								      step;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for (i = 0; i < l; i++) {
							 | 
						||
| 
								 | 
							
								    iterator = arguments[i].values();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    while ((step = iterator.next(), !step.done))
							 | 
						||
| 
								 | 
							
								      U.add(step.value);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return U;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function computing the difference between two sets.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 * @return {Set}   - The difference.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.difference = function(A, B) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // If first set is empty
							 | 
						||
| 
								 | 
							
								  if (!A.size)
							 | 
						||
| 
								 | 
							
								    return new Set();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!B.size)
							 | 
						||
| 
								 | 
							
								    return new Set(A);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var D = new Set();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var iterator = A.values(),
							 | 
						||
| 
								 | 
							
								      step;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while ((step = iterator.next(), !step.done)) {
							 | 
						||
| 
								 | 
							
								    if (!B.has(step.value))
							 | 
						||
| 
								 | 
							
								      D.add(step.value);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return D;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function computing the symmetric difference between two sets.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 * @return {Set}   - The symmetric difference.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.symmetricDifference = function(A, B) {
							 | 
						||
| 
								 | 
							
								  var S = new Set();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var iterator = A.values(),
							 | 
						||
| 
								 | 
							
								      step;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while ((step = iterator.next(), !step.done)) {
							 | 
						||
| 
								 | 
							
								    if (!B.has(step.value))
							 | 
						||
| 
								 | 
							
								      S.add(step.value);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  iterator = B.values();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while ((step = iterator.next(), !step.done)) {
							 | 
						||
| 
								 | 
							
								    if (!A.has(step.value))
							 | 
						||
| 
								 | 
							
								      S.add(step.value);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return S;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function returning whether A is a subset of B.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 * @return {boolean}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.isSubset = function(A, B) {
							 | 
						||
| 
								 | 
							
								  var iterator = A.values(),
							 | 
						||
| 
								 | 
							
								      step;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Shortcuts
							 | 
						||
| 
								 | 
							
								  if (A === B)
							 | 
						||
| 
								 | 
							
								    return true;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (A.size > B.size)
							 | 
						||
| 
								 | 
							
								    return false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while ((step = iterator.next(), !step.done)) {
							 | 
						||
| 
								 | 
							
								    if (!B.has(step.value))
							 | 
						||
| 
								 | 
							
								      return false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return true;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function returning whether A is a superset of B.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 * @return {boolean}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.isSuperset = function(A, B) {
							 | 
						||
| 
								 | 
							
								  return exports.isSubset(B, A);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function adding the items of set B to the set A.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.add = function(A, B) {
							 | 
						||
| 
								 | 
							
								  var iterator = B.values(),
							 | 
						||
| 
								 | 
							
								      step;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while ((step = iterator.next(), !step.done))
							 | 
						||
| 
								 | 
							
								    A.add(step.value);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function subtracting the items of set B from the set A.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.subtract = function(A, B) {
							 | 
						||
| 
								 | 
							
								  var iterator = B.values(),
							 | 
						||
| 
								 | 
							
								      step;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while ((step = iterator.next(), !step.done))
							 | 
						||
| 
								 | 
							
								    A.delete(step.value);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function intersecting the items of A & B.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.intersect = function(A, B) {
							 | 
						||
| 
								 | 
							
								  var iterator = A.values(),
							 | 
						||
| 
								 | 
							
								      step;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while ((step = iterator.next(), !step.done)) {
							 | 
						||
| 
								 | 
							
								    if (!B.has(step.value))
							 | 
						||
| 
								 | 
							
								      A.delete(step.value);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function disjuncting the items of A & B.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.disjunct = function(A, B) {
							 | 
						||
| 
								 | 
							
								  var iterator = A.values(),
							 | 
						||
| 
								 | 
							
								      step;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var toRemove = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while ((step = iterator.next(), !step.done)) {
							 | 
						||
| 
								 | 
							
								    if (B.has(step.value))
							 | 
						||
| 
								 | 
							
								      toRemove.push(step.value);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  iterator = B.values();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while ((step = iterator.next(), !step.done)) {
							 | 
						||
| 
								 | 
							
								    if (!A.has(step.value))
							 | 
						||
| 
								 | 
							
								      A.add(step.value);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for (var i = 0, l = toRemove.length; i < l; i++)
							 | 
						||
| 
								 | 
							
								    A.delete(toRemove[i]);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function returning the size of the intersection of A & B.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 * @return {number}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.intersectionSize = function(A, B) {
							 | 
						||
| 
								 | 
							
								  var tmp;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // We need to know the smallest set
							 | 
						||
| 
								 | 
							
								  if (A.size > B.size) {
							 | 
						||
| 
								 | 
							
								    tmp = A;
							 | 
						||
| 
								 | 
							
								    A = B;
							 | 
						||
| 
								 | 
							
								    B = tmp;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (A.size === 0)
							 | 
						||
| 
								 | 
							
								    return 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (A === B)
							 | 
						||
| 
								 | 
							
								    return A.size;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var iterator = A.values(),
							 | 
						||
| 
								 | 
							
								      step;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var I = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while ((step = iterator.next(), !step.done)) {
							 | 
						||
| 
								 | 
							
								    if (B.has(step.value))
							 | 
						||
| 
								 | 
							
								      I++;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return I;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function returning the size of the union of A & B.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 * @return {number}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.unionSize = function(A, B) {
							 | 
						||
| 
								 | 
							
								  var I = exports.intersectionSize(A, B);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return A.size + B.size - I;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function returning the Jaccard similarity between A & B.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 * @return {number}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.jaccard = function(A, B) {
							 | 
						||
| 
								 | 
							
								  var I = exports.intersectionSize(A, B);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (I === 0)
							 | 
						||
| 
								 | 
							
								    return 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var U = A.size + B.size - I;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return I / U;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Function returning the overlap coefficient between A & B.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param  {Set} A - First set.
							 | 
						||
| 
								 | 
							
								 * @param  {Set} B - Second set.
							 | 
						||
| 
								 | 
							
								 * @return {number}
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								exports.overlap = function(A, B) {
							 | 
						||
| 
								 | 
							
								  var I = exports.intersectionSize(A, B);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (I === 0)
							 | 
						||
| 
								 | 
							
								    return 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return I / Math.min(A.size, B.size);
							 | 
						||
| 
								 | 
							
								};
							 |