Skip to content

Instantly share code, notes, and snippets.

@goldhand
Created July 10, 2017 22:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save goldhand/1cfbd7a8109c61bbb147e3d0e4bd5b46 to your computer and use it in GitHub Desktop.
Save goldhand/1cfbd7a8109c61bbb147e3d0e4bd5b46 to your computer and use it in GitHub Desktop.
A map object that swings both ways ;)
/**
* @module {Function} utils/bimap
* @flow
*/
type MapKeyType = typeof BiMap.FORWARD | typeof BiMap.REVERSE;
// always sets Array<value>
// merges instead of overwrites by default
export default class BiMap<K, V> {
stores: {
[mapKey: MapKeyType]: Map<K, Array<V>>,
}
static FORWARD: 'FORWARD' = 'FORWARD';
static REVERSE: 'REVERSE' = 'REVERSE';
constructor() {
Object.defineProperty(this, 'stores', {
enumerable: false,
configurable: false,
writable: false,
value: {
[BiMap.FORWARD]: new Map(),
[BiMap.REVERSE]: new Map(),
},
});
Object.freeze(this.stores);
}
get forward(): * {
return this.mapFacade(BiMap.FORWARD);
}
get reverse(): * {
return this.mapFacade(BiMap.REVERSE);
}
mapFacade(mapKey: MapKeyType): * {
return {
get: this.mapGet(mapKey),
has: this.mapHas(mapKey),
keys: this.mapKeys(mapKey),
values: this.mapValues(mapKey),
entries: this.mapEntries(mapKey),
clear: this.mapClear(mapKey),
set: this.mapSet(mapKey),
delete: this.mapDelete(mapKey),
remove: this.mapRemove(mapKey),
size: this.mapSize(mapKey),
};
}
// Map facade methods
mapGet = (mapKey: MapKeyType) => (key: K): Array<V> => this.stores[mapKey].get(key) || [];
mapHas = (mapKey: MapKeyType) => (key: K): boolean => this.stores[mapKey].has(key);
mapKeys = (mapKey: MapKeyType) => () => this.stores[mapKey].keys();
mapValues = (mapKey: MapKeyType) => () => this.stores[mapKey].values();
mapEntries = (mapKey: MapKeyType) => () => this.stores[mapKey].entries();
mapClear = (mapKey: MapKeyType) => () => this.stores[mapKey].clear();
mapSize = (mapKey: MapKeyType) => () => this.stores[mapKey].size;
mapSet = (mapKey: MapKeyType) =>
/**
* Set a value on the forwardMap
*
* Map will always save value into an array. By default, values are merged
* with any existing values for the provided key (if they exist).
*
* @alias {Function} mapSet
* @param {*} key Key to set on forwardMap
* @param {*} value Value mapped to forwardMap[key]
* @param {boolean} [merge = true] Should value be merged into an
* existing value for provided key
* @returns {Map} The forwardMap
*/
(key: K, value: V, merge: boolean = true): Map<K, Array<V>> => {
if (!merge) return this.stores[mapKey].set(key, [value]);
return this.stores[mapKey].set(key, [...this.mapGet(mapKey)(key), value]);
}
mapDelete = (mapKey: MapKeyType) =>
(key: K): boolean => this.stores[mapKey].delete(key);
mapRemove = (mapKey: MapKeyType) => (key: K, value: V): boolean => {
const hasKey = this.mapHas(mapKey)(key);
if (hasKey) {
this.mapSet(mapKey)(key, this.mapGet(mapKey)(key).filter(v => v !== value));
}
return hasKey; // return boolean
}
set(key: K, value: V, mergeForward: boolean = true, mergeReverse: boolean = true): BiMap<K, V> {
this.forward.set(key, value, mergeForward);
this.reverse.set(value, key, mergeReverse);
return this;
}
// TODO: cool idea but this doesn't make sense really.
related(key: K): Array<Array<V>> {
if (this.reverse.has(key)) return this.reverse.get(key).map(k => this.forward.get(k));
if (this.forward.has(key)) return this.forward.get(key).map(k => this.reverse.get(k));
// if key doesn't exist return Array<Array<>>
return [[]];
}
/**
* Check if either map has(key)
* @param {*} key - key to check
* @returns {boolean} - if either forward or reverse have provided key
*/
has(key: K): boolean {
return this.reverse.has(key) || this.forward.has(key);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment