1
1
import type { Composite } from "../composite.ts" ;
2
2
import { isNaN , NaN , apply , ownKeys , keyFor , weakMapGet , weakMapSet , sort , localeCompare } from "./originals.ts" ;
3
3
import { assert } from "./utils.ts" ;
4
+ import { randomHash , MurmurHashStream , type Hasher } from "./murmur.ts" ;
4
5
5
- const seed = randomHash ( ) ;
6
6
const TRUE = randomHash ( ) ;
7
7
const FALSE = randomHash ( ) ;
8
8
const NULL = randomHash ( ) ;
9
9
const UNDEFINED = randomHash ( ) ;
10
10
const SYMBOLS = randomHash ( ) ;
11
11
const KEY = randomHash ( ) ;
12
+ const OBJECTS = randomHash ( ) ;
12
13
13
14
const hashCache = new WeakMap < symbol | object , number | typeof lazyCompositeHash > ( ) ;
14
15
const symbolsInWeakMap = ( ( ) => {
@@ -35,93 +36,90 @@ export function maybeHashComposite(input: Composite): number | undefined {
35
36
return undefined ;
36
37
}
37
38
38
- // TODO - use a better hashing function
39
- /** A very basic, demonstrative, non-cryptographic, hashing function for Composites */
40
39
export function hashComposite ( input : Composite ) : number {
41
- let hash = maybeHashComposite ( input ) ;
42
- if ( hash !== undefined ) {
43
- return hash ;
40
+ const cachedHash = maybeHashComposite ( input ) ;
41
+ if ( cachedHash !== undefined ) {
42
+ return cachedHash ;
44
43
}
45
- hash = 0 ;
44
+ const hasher = new MurmurHashStream ( ) ;
46
45
const keys = apply ( ownKeys , null , [ input ] ) ;
47
46
apply ( sort , keys , [ keySort ] ) ;
48
47
for ( let i = 0 ; i < keys . length ; i ++ ) {
49
48
const key = keys [ i ] ;
50
49
if ( typeof key === "string" ) {
51
- hash ^= stringHash ( key ) ^ KEY ;
52
- hash ^= hashValue ( input [ key as keyof typeof input ] ) ;
50
+ hasher . update ( KEY ) ;
51
+ hasher . update ( key ) ;
52
+ updateHasher ( hasher , input [ key as keyof typeof input ] ) ;
53
53
continue ;
54
54
}
55
55
assert ( typeof key === "symbol" ) ;
56
56
if ( ! symbolsInWeakMap && keyFor ( key ) === undefined ) {
57
- // Remaining keys can't be hashed
57
+ // Remaining keys can't be hashed in this JS engine
58
58
break ;
59
59
}
60
- hash ^= symbolHash ( key ) ^ KEY ;
61
- hash ^= hashValue ( input [ key as keyof typeof input ] ) ;
60
+ hasher . update ( KEY ) ;
61
+ symbolUpdateHasher ( hasher , key ) ;
62
+ updateHasher ( hasher , input [ key as keyof typeof input ] ) ;
62
63
}
63
64
assert ( apply ( weakMapGet , hashCache , [ input ] ) === lazyCompositeHash ) ;
65
+ const hash = hasher . digest ( ) ;
64
66
apply ( weakMapSet , hashCache , [ input , hash ] ) ;
65
67
return hash ;
66
68
}
67
69
68
- function hashValue ( input : unknown ) : number {
70
+ function updateHasher ( hasher : Hasher , input : unknown ) : void {
69
71
if ( input === null ) {
70
- return NULL ;
72
+ hasher . update ( NULL ) ;
73
+ return ;
71
74
}
72
75
switch ( typeof input ) {
73
76
case "undefined" :
74
- return UNDEFINED ;
77
+ hasher . update ( UNDEFINED ) ;
78
+ return ;
75
79
case "boolean" :
76
- return input ? TRUE : FALSE ;
80
+ hasher . update ( input ? TRUE : FALSE ) ;
81
+ return ;
77
82
case "number" :
78
- return numberHash ( input ) ;
83
+ // Normalize NaNs and -0
84
+ hasher . update ( isNaN ( input ) ? NaN : input === 0 ? 0 : input ) ;
85
+ return ;
79
86
case "bigint" :
80
- return numberHash ( Number ( input ) ) ;
81
87
case "string" :
82
- return stringHash ( input ) ;
88
+ hasher . update ( input ) ;
89
+ return ;
83
90
case "symbol" :
84
- return symbolHash ( input ) ;
91
+ symbolUpdateHasher ( hasher , input ) ;
92
+ return ;
85
93
case "object" :
86
- return cachedHash ( input ) ;
87
94
case "function" :
88
- return cachedHash ( input ) ;
95
+ hasher . update ( cachedHash ( input ) ) ;
96
+ return ;
89
97
default :
90
98
throw new TypeError ( `Unsupported input type: ${ typeof input } ` ) ;
91
99
}
92
100
}
93
101
94
- const floatArray = new Float64Array ( 1 ) ;
95
- const intArray = new Uint32Array ( floatArray . buffer ) ;
96
- function numberHash ( input : number ) : number {
97
- floatArray [ 0 ] = input === 0 ? 0 : isNaN ( input ) ? NaN : input ;
98
- const hash = intArray [ 0 ] ^ intArray [ 1 ] ;
99
- return hash >>> 0 ;
100
- }
101
-
102
- function stringHash ( input : string ) : number {
103
- let hash = seed ;
104
- for ( let i = 0 ; i < input . length ; i ++ ) {
105
- hash = ( hash * 33 ) ^ input . charCodeAt ( i ) ;
106
- }
107
- return hash >>> 0 ;
108
- }
109
-
110
- function symbolHash ( input : symbol ) : number {
102
+ function symbolUpdateHasher ( hasher : Hasher , input : symbol ) : void {
111
103
const regA = Symbol . keyFor ( input ) ;
112
104
if ( regA !== undefined ) {
113
- return stringHash ( regA ) ^ SYMBOLS ;
105
+ hasher . update ( SYMBOLS ) ;
106
+ hasher . update ( regA ) ;
107
+ return ;
114
108
}
115
109
if ( ! symbolsInWeakMap ) {
116
- return SYMBOLS ;
110
+ hasher . update ( SYMBOLS ) ;
111
+ return ;
112
+ } else {
113
+ hasher . update ( cachedHash ( input ) ) ;
117
114
}
118
- return cachedHash ( input ) ;
119
115
}
120
116
117
+ let nextObjectId = 1 ;
121
118
function cachedHash ( input : object | symbol ) : number {
122
119
let hash = apply ( weakMapGet , hashCache , [ input ] ) ;
123
120
if ( hash === undefined ) {
124
- hash = randomHash ( ) ;
121
+ hash = nextObjectId ^ OBJECTS ;
122
+ nextObjectId ++ ;
125
123
apply ( weakMapSet , hashCache , [ input , hash ] ) ;
126
124
return hash ;
127
125
}
@@ -131,10 +129,6 @@ function cachedHash(input: object | symbol): number {
131
129
return hash ;
132
130
}
133
131
134
- function randomHash ( ) {
135
- return ( Math . random ( ) * ( 2 ** 31 - 1 ) ) >>> 0 ;
136
- }
137
-
138
132
/**
139
133
* Strings before symbols.
140
134
* Strings sorted lexicographically.
0 commit comments