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
3 years ago
|
/**
|
||
|
* 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;
|