-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from thalesrc/smart-map
Smart map
- Loading branch information
Showing
4 changed files
with
198 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { expect, assert } from 'chai'; | ||
import 'mocha'; | ||
|
||
import { SmartMap } from "./smart-map"; | ||
|
||
describe('SmartMap Class', () => { | ||
let map: SmartMap; | ||
|
||
beforeEach(() => { | ||
map = new SmartMap(); | ||
}); | ||
|
||
it('should construct properly', () => { | ||
const mapWithValues = new SmartMap([['key', 'value'], ['key2', 'value2']]); | ||
|
||
assert.isDefined(map); | ||
assert.isDefined(mapWithValues); | ||
expect([...mapWithValues.entries()]).to.be.eql([...new Map([['key', 'value'], ['key2', 'value2']]).entries()]); | ||
}); | ||
|
||
it('should set a value by primitive type keys', () => { | ||
map.set(1, "number"); | ||
map.set("string", "string"); | ||
map.set(true, "boolean"); | ||
map.set(Symbol(), "symbol"); | ||
map.set(undefined, "undefined"); | ||
|
||
expect(map.size).to.eq(5); | ||
}); | ||
|
||
it('should set a value by object type keys', () => { | ||
const anObj = {}; | ||
|
||
map.set(anObj, "foo"); | ||
expect(map.has(anObj)).to.be.true; | ||
}); | ||
|
||
it('should store object keys in weakmap storage', () => { | ||
const anObj = {}; | ||
|
||
map.set(anObj, "foo"); | ||
|
||
expect(map.size).to.eq(0); | ||
expect(map.has(anObj)).to.be.true; | ||
}); | ||
|
||
it('should check correctly when has method called via both primitive and object keys', () => { | ||
const anObj = {}; | ||
|
||
map.set(1, "1"); | ||
map.set(anObj, "an object"); | ||
|
||
expect(map.has(1)).to.be.true; | ||
expect(map.has(anObj)).to.be.true; | ||
}); | ||
|
||
it('should get value when get method called via both primitive and object keys', () => { | ||
const anObj = {}; | ||
|
||
map.set(1, "1"); | ||
map.set(anObj, "an object"); | ||
|
||
expect(map.get(1)).to.eq("1"); | ||
expect(map.get(anObj)).to.eq("an object"); | ||
}); | ||
|
||
it('should delete value when delete method called via both primitive and object key', () => { | ||
const anObj = {}; | ||
|
||
map.set(1, "1"); | ||
map.set(anObj, "an object"); | ||
|
||
const primitiveResult = map.delete(1); | ||
const objectResult = map.delete(anObj); | ||
|
||
expect(primitiveResult).to.be.true; | ||
expect(objectResult).to.be.true; | ||
|
||
expect(map.size).to.eq(0); | ||
expect(map.has(anObj)).to.be.false; | ||
}) | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// Symbols to protect overriding private methods & properties of SmartMap Class | ||
const KEY__WEAKMAP: unique symbol = Symbol('SmartMap.weakMap'); | ||
|
||
/** | ||
* ### SmartMap | ||
* | ||
* Like WeakMap but can also store values using primitive keys | ||
* | ||
* * * * | ||
* Example usage: | ||
* ```typescript | ||
* import { SmartMap } from "@thalesrc/js-utils"; | ||
* | ||
* const aMap = new SmartMap(); | ||
* | ||
* aMap.set("foo", "foo"); | ||
* aMap.set(1, "thales rocks"); | ||
* | ||
* console.log(aMap.size) // 2 | ||
* | ||
* aMap.set({}, "thales rocks again"); | ||
* console.log(aMap.size) // 2 | ||
* | ||
* const anObject = {}; | ||
* aMap.set(anObject, "thales rocks again and again"); | ||
* console.log(aMap.size) // 3 | ||
* console.log(aMap.get(anObject)) // "thales rocks again and again" | ||
* ``` | ||
* * * * | ||
* @template K Typeof Key | ||
* @template V Typeof Value | ||
*/ | ||
export class SmartMap<K = any, V = any> extends Map<K, V> { | ||
/** | ||
* Symbol refference of weakmap property | ||
*/ | ||
protected static readonly KEY__WEAKMAP = KEY__WEAKMAP; | ||
|
||
/** | ||
* Allowed key types to store them in a weakmap instance | ||
*/ | ||
public static readonly TYPES_TO_STORE_IN_WEAKMAP = Object.freeze(['object', 'function']); | ||
|
||
/** | ||
* The WeakMap object to store values with associated non-primitive (object) keys | ||
*/ | ||
protected [KEY__WEAKMAP] = new WeakMap<Object, V>(); | ||
|
||
/** | ||
* Returns a boolean asserting whether a value has been associated to the key in the SmartMap object or not. | ||
* | ||
* @param key The key of the element to test for presence in the SmartMap object. | ||
* @returns A boolean asserting whether a value has been associated to the key in the SmartMap object or not. | ||
*/ | ||
public has(key: K): boolean { | ||
if (this.isStorableInWeakMap(key)) { | ||
return this[KEY__WEAKMAP].has(key); | ||
} | ||
|
||
return super.has(key); | ||
} | ||
|
||
/** | ||
* Returns the value associated to the key, or undefined if there is none. | ||
* | ||
* @param key The key of the element to return from the SmartMap object. | ||
* @returns The value associated to the key | ||
*/ | ||
public get(key: K): V { | ||
if (this.isStorableInWeakMap(key)) { | ||
return this[KEY__WEAKMAP].get(key); | ||
} | ||
|
||
return super.get(key); | ||
} | ||
|
||
/** | ||
* Sets the value for the key in the SmartMap object. | ||
* | ||
* @param key The key of the element to add to the SmartMap object. | ||
* @param value The value of the element to add to the SmartMap object. | ||
* @returns Returns the SmartMap object. | ||
*/ | ||
public set(key: K, value: V): this { | ||
if (this.isStorableInWeakMap(key)) { | ||
this[KEY__WEAKMAP].set(key, value); | ||
} else { | ||
super.set(key, value); | ||
} | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Removes the associated value from the SmartMap object, | ||
* | ||
* @param key The key of the element to remove from the SmartMap object. | ||
* @returns `true` if an element in the SmartMap object existed and has been removed, or `false` if the element does not exist. `SmartMap.prototype.has(key)` will return false afterwards. | ||
*/ | ||
public delete(key: K): boolean { | ||
return this[KEY__WEAKMAP].delete(key) || super.delete(key); | ||
} | ||
|
||
/** | ||
* Returns a boolean whether if the key is storable in weakmap or not | ||
* | ||
* @param key Key to check if it can be stored in weakmap | ||
* @returns A boolean whether if the key is storable in weakmap or not | ||
*/ | ||
protected isStorableInWeakMap(key: K): boolean { | ||
return SmartMap.TYPES_TO_STORE_IN_WEAKMAP.some(_key => typeof key === _key); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters