Skip to content

Commit d6c6041

Browse files
committed
Initial version
0 parents  commit d6c6041

14 files changed

Lines changed: 2953 additions & 0 deletions

.editorconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# http://editorconfig.org
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
[*]
7+
charset = utf-8
8+
insert_final_newline = true
9+
10+
[*.{js,jsx,ts,tsx}]
11+
indent_style = space
12+
indent_size = 4
13+
14+
[*.json]
15+
indent_style = space
16+
indent_size = 4

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/node_modules
2+
/dist
3+
/*.sh
4+
/*.bat
5+
/*.tgz
6+
.vscode/
7+
8+
*.log

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Changelog
2+
All notable changes to the Reactodia will be documented in this document.
3+
4+
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
5+
6+
## [0.1.0]
7+
- Initial release with:
8+
* Collection classes: `HashMap`, `HashSet`.
9+
* Interface types: `ReadonlyHashMap`, `ReadonlyHashSet`.
10+
* Hash functions: `chainHash()`, `dropHighestNonSignBit()`, `hashBigInt()`, `hashNumber()`, `hashString()`, `hashTuple()`, `hashValue()`.
11+
12+
[0.1.0]: https://github.com/reactodia/reactodia-hashmap/compare/v0.0.0...v0.1.0

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Alexey Morozov
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Reactodia Hashmap [![npm version](https://badge.fury.io/js/@reactodia%2Fhashmap.svg)](https://badge.fury.io/js/@reactodia%2Fhashmap)
2+
3+
`@reactodia/hashmap` is a TypeScript/JavaScript library that provides `HashMap` and `HashSet` collections to use as a compatible replacement for the built-in [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) and [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) when keys (items) are composite values.
4+
5+
## Installation
6+
7+
Install with:
8+
```sh
9+
npm install --save @reactodia/hashmap
10+
```
11+
12+
## Quick example
13+
14+
```ts
15+
import { HashMap, hashTuple } from '@reactodia/hashmap';
16+
17+
type Edge = { from: string; to: string };
18+
const hashEdge = (e: Edge) => hashTuple(e.from, e.to);
19+
const equalEdges = (a: Edge, b: Edge) => (
20+
a.from === b.from &&
21+
a.to === b.to
22+
);
23+
24+
// Create a map with required hash and equality functions for keys
25+
const map = new HashMap<Edge, number>(hashEdge, equalEdges);
26+
27+
// Set values for keys
28+
map.set({ from: 'A', to: 'B' }, 10);
29+
map.set({ from: 'C', to: 'D' }, 20);
30+
31+
// Update value for an equal key with a different object identity
32+
map.set({ from: 'A', to: 'B' }, 100);
33+
34+
// Get an existing value by the equal key with a different identity
35+
const value = map.get({ from: 'A', to: 'B' });
36+
37+
// Iterate over entries in the original insertion order
38+
for (const [key, value] of map) {
39+
console.log('Map entry: ', key, value);
40+
}
41+
42+
// Clone the map with the same hash and equality functions
43+
const clonedMap = map.clone();
44+
45+
// Delete entry for an equal key with a different object identity
46+
map.delete({ from: 'A', to: 'B' });
47+
48+
// Clear the map
49+
map.clear();
50+
```
51+
52+
## API
53+
54+
The library has the following exports:
55+
56+
#### `HashMap<K, V>` class
57+
58+
Fully compatible with built-in [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) with a different constructor and an additional method.
59+
60+
The `HashMap` keeps the entries in the original insertion order the same way as the built-in `Map` does.
61+
62+
##### `HashMap<K, V>(hash, equals, entries?)` constructor
63+
64+
Constructs a new hash map with the specified hash and equality functions for keys.
65+
66+
Parameters:
67+
* `hash: (k: Key) => number`
68+
* `equals: (k1, k2) => boolean`
69+
* `entries?: Iterable<readonly [K, V]>`
70+
71+
##### `HashMap<K, V>.clone(): HashMap<K, V>` method
72+
73+
Returns a copy of the map with the same entries, hash and equality functions for the keys.
74+
75+
#### `HashSet<T>` class
76+
77+
Fully compatible with built-in [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) with a different constructor and an additional method.
78+
79+
The `HashSet` keeps the entries in the original insertion order the same way as the built-in `Set` does.
80+
81+
##### `HashSet<T>(hash, equals, items?)` constructor
82+
83+
Constructs a new hash set with the specified hash and equality functions for items.
84+
85+
Parameters:
86+
* `hash: (v: T) => number`
87+
* `equals: (v1, v2) => boolean`
88+
* `items?: Iterable<T>`
89+
90+
##### `HashSet<T>.clone(): HashMap<T>` method
91+
92+
Returns a copy of the set with the same items, hash and equality functions for the items.
93+
94+
#### `ReadonlyHashMap<K, V>` interface
95+
96+
TypeScript interface for a read-only version of a `HashMap` which is compatible with `ReadonlyMap`.
97+
98+
#### `ReadonlyHashSet<T>` interface
99+
100+
TypeScript interface for a read-only version of a `HashSet` which is compatible with `ReadonlySet`.
101+
102+
#### `hashString(value: string): number` function
103+
104+
Utility function to compute a string hash using [FNV-32a](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function) algorithm.
105+
106+
#### `hashNumber(value: number): number` function
107+
108+
Utility function to compute a hash for a JS number:
109+
* for 32-bit integers the hash is the number itself;
110+
* otherwise the hash is `Math.imul(31, H) + L` where `H` and `L` are 32-bit parts of IEEE floating point representation of the number.
111+
112+
#### `hashBigInt(value: bigint): number` function
113+
114+
Utility function to compute a hash for a `bigint` value with the following formula: `abs(N) % 0x1_0000_0000` where N is the `bigint` value.
115+
116+
#### `hashValue(value: string | number | bigint | boolean | undefined | null)`
117+
118+
Utility function to compute a hash for a primitive JS value:
119+
* for `string` the `hashString(value)` is used;
120+
* for `number` the `hashNumber(value)` is used;
121+
* for `bigint` the `hashBigInt(value)` is used;
122+
* for `null` the hash is `0`;
123+
* for `undefined` the hash is `1`;
124+
* for `boolean` the hash is `3` for `false` and `4` for `true`;
125+
* otherwise the hash is `0`.
126+
127+
#### `chainHash(hash: number, added: number): number`
128+
129+
Utility function to "chain" hash values when computing a hash of a composite value, for example:
130+
131+
```ts
132+
let hash = 0;
133+
hash = chainHash(hash, hashValue(key.fieldA));
134+
hash = chainHash(hash, hashValue(key.fieldB));
135+
hash = chainHash(hash, hashValue(key.fieldC));
136+
```
137+
138+
The formula for chained hash is `(Math.imul(hash, 31) + added) | 0`.
139+
140+
#### `hashTuple(...values): number`
141+
142+
Utility function to compute hash for a tuple of primitive values, equivalent to subsequent calls to `chainHash()` with `hashValue()` for each argument with the initial hash value of a hashed tuple length:
143+
144+
```ts
145+
const hash = hashTuple(key.fieldA, key.fieldB, key.fieldC);
146+
// same as
147+
let hash = hashNumber(3);
148+
hash = chainHash(hash, hashValue(key.fieldA));
149+
hash = chainHash(hash, hashValue(key.fieldB));
150+
hash = chainHash(hash, hashValue(key.fieldC));
151+
```
152+
153+
#### `dropHighestNonSignBit(hash: number): number`
154+
155+
Utility function which coerces a number value into an integer such that "small integer optimization" ([Smi](https://v8.dev/blog/pointer-compression)) in V8 engine can be applied.
156+
157+
Passing the value from this function to return a computed hash may improve performance in some specific situations.
158+
159+
## License
160+
161+
The library is distributed under MIT license, see [LICENSE](./LICENSE).

0 commit comments

Comments
 (0)