mapam
v1.0.0
Published
A bidirectional Map/WeakMap implementation with the same API as an ES6 Map/WeakMap!
Downloads
51
Maintainers
Readme
- Tiny: less than 720 bytes minzipped including dependencies!
- Familiar: uses the same API as an ES6 Map/WeakMap
- Compliant: maintains all the invariants of Map/WeakMap including method return values and even iteration order!
- Correct: handles edge cases correctly
Install
$ npm i mapam
Huh?
A bidirectional map is like a regular map except it preserves uniqueness of its values (in addition to its keys) and supports an inverse view, which is another bidirectional map containing and backed by the same entries, but with reversed keys and values.
For a weak bidirectional map specifically, its values (in addition to its keys) must be objects.
Usage
The API of one side/view of the bidirectional map is identical to the
Map
/WeakMap
API. Just use like a normal Map
or WeakMap
and call the
inverse
map to get the other side of the bidirectional map!
import { BiMap, WeakBiMap } from 'mapam'
const biMap = new BiMap()
biMap.set(1, `one`)
biMap.set(2, `two`)
biMap.inverse().set(`three`, 3)
console.log([...biMap])
//=> [ [ 1, 'one' ], [ 2, 'two' ], [ 3, 'three' ] ]
console.log([...biMap.inverse()])
//=> [ [ 'one', 1 ], [ 'two', 2 ], [ 'three', 3 ] ]
console.log(biMap.get(2))
//=> two
console.log(biMap.inverse().get(`two`))
//=> 2
biMap.inverse().delete(`two`)
console.log(biMap.has(2))
//=> false
console.log([...biMap])
//=> [ [ 1, 'one' ], [ 3, 'three' ] ]
try {
// Throws!
biMap.set(10, `three`)
} catch (error) {
console.log(error.message)
//=> value already bound to another key
}
// Doesn't throw!
// Conveys intention of changing the value associated with the given key
biMap.inverse().set(`three`, 10)
// Also doesn't throw
biMap.set(10, `three`, { force: true })
const weakBiMap = new WeakBiMap()
const a = {}
const b = {}
weakBiMap.set(a, b)
console.log(weakBiMap.get(a) === b)
//=> true
console.log(weakBiMap.inverse().get(b) === a)
//=> true
See the type definitions for more documentation.
Correctness
| Category | mapam
| [email protected]
| [email protected]
| [email protected]
| @rimbu/[email protected]
|
| --------------- | ------------------ | -------------- | ----------- | ------------------ | -------------------- |
| Iteration order | :heavy_check_mark: | :x: | :x: | :x: | :x: |
| Negative zero | :heavy_check_mark: | :x: | :x: | :x: | :x: |
Iteration order
Map
(and many other "unordered" data structures) allow iteration in insertion
order so it's only logical for a bidirectional map to support iteration in a
logical order like insertion order.
const biMap = new BiMap()
biMap.set(1, `one`)
biMap.set(2, `two`)
biMap.set(3, `three`)
biMap.set(4, `four`)
biMap.inverse().set(`two`, -2)
biMap.delete(3)
console.log([...biMap])
// Correct behavior:
// mapam => [ [ 1, 'one' ], [ -2, 'two' ], [ 4, 'four' ] ]
// Incorrect behaviors:
// [email protected] => Doesn't support iteration
// [email protected] => [ [ 1, 'one' ], [ 4, 'four' ], [ -2, 'two' ] ]
// [email protected] => [ [ 1, 'one' ], [ 4, 'four' ], [ -2, 'two' ] ]
// @rimbu/[email protected] => [ [ 1, 'one' ], [ 4, 'four' ], [ -2, 'two' ] ]
Negative zero
A key in a Map
can only occur once. But how is the key's uniqueness
determined? JavaScript's Map
uses the
sameValueZero
algorithm
when checking if two keys are equal. The algorithm considers +0 and -0 to be
equal, but they are actually two different values due to how
IEEE floating point numbers
work.
This means that Map
coerces -0 to +0 for its keys, but not for its values. So
if a bidirectional map implementation uses Map
internally, then it must either
coerce -0 to +0 for its values as well or
somehow broaden its keys to support
both -0 and +0.
const biMap = new BiMap()
biMap.set(`negative-zero`, -0)
biMap.set(`zero`, 0)
console.log(
biMap.get(`negative-zero`),
biMap.get(`zero`),
biMap.inverse().get(-0),
biMap.inverse().get(0),
)
// Correct behavior:
// mapam => -0 0 negative-zero zero
// Incorrect behaviors:
// [email protected] => -0 0 negative-zero negative-zero
// [email protected] => undefined 0 zero zero
// [email protected] => undefined 0 zero zero
// @rimbu/[email protected] => -0 0 negative-zero negative-zero
Contributing
Stars are always welcome!
For bugs and feature requests, please create an issue.
For pull requests, please read the contributing guidelines.
License
This is not an official Google product.