11import type { NestedKeyOf } from './types'
22import { Effect } from 'effect'
3- import { normalizeText } from './utils'
3+ import { addBitVectors , bitwiseAND , bitwiseNOT , bitwiseOR , bitwiseShiftLeft , bitwiseXOR , countSetBits , maskVP , normalizeText , setLeastSignificantBit } from './utils'
44
55export class QuantumMatcher < T > {
66 private collection : T [ ]
7-
87 private pathCache = new Map < string , string [ ] > ( )
98
109 constructor (
@@ -19,7 +18,7 @@ export class QuantumMatcher<T> {
1918 public findMatches ( query : string ) : { item : T , score : number , matches : [ number , number ] [ ] } [ ] {
2019 return Effect . try ( {
2120 try : ( ) => {
22- const queryParts = query . split ( ' ' ) . map ( normalizeText ) // Split and normalize query parts
21+ const queryParts = query . split ( ' ' ) . map ( normalizeText )
2322 const results : { item : T , score : number , matches : [ number , number ] [ ] } [ ] = [ ]
2423
2524 for ( const item of this . collection ) {
@@ -37,26 +36,30 @@ export class QuantumMatcher<T> {
3736 const m = queryPart . length
3837 const n = normalizedText . length
3938
40- let VP = ( 1n << BigInt ( m ) ) - 1n
41- let HN = 0n
42- let HP = 0n
39+ let VP = this . createInitialVP ( m )
40+ let HN : number [ ] = [ ]
41+ let HP : number [ ] = [ ]
4342 let score = 0
4443 let matches : [ number , number ] [ ] = [ ]
4544
4645 for ( let j = 0 ; j < n ; j ++ ) {
4746 const char = normalizedText [ j ]
48- const EQ = queryMask . get ( char ) || 0n
47+ const EQ = queryMask . get ( char ) || [ ]
48+
49+ const X = bitwiseOR ( EQ , HP )
50+ const X_and_VP = bitwiseAND ( X , VP )
51+ const sum = addBitVectors ( VP , X_and_VP )
52+ const sum_xor_VP = bitwiseXOR ( sum , VP )
53+ const D0 = bitwiseOR ( sum_xor_VP , X )
4954
50- const X = EQ | HP
51- const D0 = ( ( VP + ( X & VP ) ) ^ VP ) | X
52- HN = VP & D0
53- HP = VP | ~ ( D0 | HN )
55+ const HN_new = bitwiseAND ( VP , D0 )
56+ const D0_or_HN = bitwiseOR ( D0 , HN_new )
57+ const HP_new = bitwiseOR ( VP , bitwiseNOT ( D0_or_HN ) )
5458
55- HP = ( HP << 1n ) | 1n
56- HN <<= 1n
59+ HP = setLeastSignificantBit ( bitwiseShiftLeft ( HP_new ) )
60+ HN = bitwiseShiftLeft ( HN_new )
5761
58- VP = HP | ~ ( D0 | HN )
59- VP &= ( 1n << BigInt ( m ) ) - 1n
62+ VP = maskVP ( bitwiseOR ( HP , bitwiseNOT ( bitwiseOR ( D0 , HN ) ) ) , m )
6063
6164 const currentScore = this . calculateMatchQuality ( VP , m , j , normalizedText , queryPart )
6265 if ( currentScore > score ) {
@@ -75,7 +78,6 @@ export class QuantumMatcher<T> {
7578 allMatches = allMatches . concat ( bestMatchesForPart )
7679 }
7780
78- // Only add results with a significant total score
7981 if ( totalScore / queryParts . length > 0.5 ) {
8082 results . push ( { item, score : totalScore / queryParts . length , matches : allMatches } )
8183 }
@@ -93,129 +95,126 @@ export class QuantumMatcher<T> {
9395 if ( obj == null )
9496 return undefined
9597
96- // Cache path arrays
9798 let pathArray = this . pathCache . get ( path )
9899 if ( ! pathArray ) {
99100 pathArray = path . split ( '.' )
100101 this . pathCache . set ( path , pathArray )
101102 }
102103
103- const length = pathArray . length
104104 let current = obj
105- for ( let i = 0 ; i < length ; i ++ ) {
105+ for ( const part of pathArray ) {
106106 if ( current == null )
107107 return undefined
108- current = current [ pathArray [ i ] ]
108+ current = current [ part ]
109109 }
110110 return current
111111 }
112112
113- private createCharMask ( text : string ) : Map < string , bigint > {
114- return Effect . try ( {
115- try : ( ) => {
116- const maskMap = new Map < string , bigint > ( )
117- for ( let i = 0 ; i < text . length ; i ++ ) {
118- const char = text [ i ]
119- maskMap . set ( char , ( maskMap . get ( char ) || 0n ) | ( 1n << BigInt ( i ) ) )
120- }
121- return maskMap
122- } ,
123- catch : error => new Error ( `[createCharMask]: Failed to create char mask: ${ error } ` ) ,
124- } ) . pipe ( Effect . runSync )
113+ private createCharMask ( text : string ) : Map < string , number [ ] > {
114+ const maskMap = new Map < string , number [ ] > ( )
115+ const chunkCount = Math . ceil ( text . length / 32 )
116+
117+ for ( let i = 0 ; i < text . length ; i ++ ) {
118+ const char = text [ i ]
119+ const chunkIndex = Math . floor ( i / 32 )
120+ const bitPosition = i % 32
121+
122+ // eslint-disable-next-line unicorn/no-new-array
123+ let chunks = maskMap . get ( char ) || new Array ( chunkCount ) . fill ( 0 )
124+ if ( chunks . length < chunkCount ) {
125+ chunks = [ ...chunks , ...Array . from ( { length : chunkCount - chunks . length } ) . fill ( 0 ) ]
126+ }
127+
128+ chunks [ chunkIndex ] |= 1 << bitPosition
129+ maskMap . set ( char , chunks )
130+ }
131+
132+ for ( const [ char , chunks ] of maskMap ) {
133+ if ( chunks . length < chunkCount ) {
134+ maskMap . set ( char , [ ...chunks , ...Array . from < number > ( { length : chunkCount - chunks . length } ) . fill ( 0 ) ] )
135+ }
136+ }
137+
138+ return maskMap
139+ }
140+
141+ private createInitialVP ( m : number ) : number [ ] {
142+ const chunkCount = Math . ceil ( m / 32 )
143+ // eslint-disable-next-line unicorn/no-new-array
144+ const vp = new Array ( chunkCount ) . fill ( 0 )
145+ for ( let i = 0 ; i < m ; i ++ ) {
146+ const chunkIndex = Math . floor ( i / 32 )
147+ const bitPosition = i % 32
148+ vp [ chunkIndex ] |= 1 << bitPosition
149+ }
150+ return vp
125151 }
126152
127153 private calculateMatchQuality (
128- VP : bigint ,
154+ VP : number [ ] ,
129155 m : number ,
130156 currentIndex : number ,
131157 normalizedText : string ,
132158 query : string ,
133159 ) : number {
134- return Effect . try ( {
135- try : ( ) => {
136- // Count the number of set bits (matches) in VP
137- let matchCount = 0
138- let mask = VP
139-
140- while ( mask !== 0n ) {
141- if ( ( mask & 1n ) === 1n ) {
142- matchCount ++
143- }
144- mask >>= 1n
145- }
146-
147- // Calculate the match ratio (matches / query length)
148- const matchRatio = matchCount / m
149-
150- // Penalize matches that are not contiguous or not aligned properly
151- const isContiguous = this . isContiguousMatch ( VP , m )
152- const alignmentPenalty = isContiguous ? 1 : 0.2 // Reduce score for non-contiguous matches
153-
154- // Calculate position bonus (matches at the start of the text score higher)
155- const positionBonus = matchRatio > 0.5 ? ( normalizedText . length - currentIndex ) / normalizedText . length : 0
156-
157- // Calculate partial match bonus (reward partial matches)
158- const partialMatchBonus = normalizedText . includes ( query ) ? 1 : 0
159-
160- // Combine factors to calculate score
161- const score = (
162- ( matchRatio * 0.6 ) // 60% weight
163- + ( alignmentPenalty * 0.3 ) // 30% weight
164- + ( positionBonus * 0.05 ) // 5% weight
165- + ( partialMatchBonus * 0.05 ) // 5% weight
166- )
167-
168- return Math . min ( score , 1 ) // Ensure score does not exceed 1
169- } ,
170- catch : error => new Error ( `[calculateMatchQuality]: Failed to calculate match quality: ${ error } ` ) ,
171- } ) . pipe ( Effect . runSync )
160+ const matchCount = countSetBits ( VP )
161+ const matchRatio = matchCount / m
162+ const isContiguous = this . isContiguousMatch ( VP , m )
163+ const alignmentPenalty = isContiguous ? 1 : 0.2
164+ const positionBonus = matchRatio > 0.5 ? ( normalizedText . length - currentIndex ) / normalizedText . length : 0
165+ const partialMatchBonus = normalizedText . includes ( query ) ? 1 : 0
166+
167+ const score = (
168+ ( matchRatio * 0.6 )
169+ + ( alignmentPenalty * 0.3 )
170+ + ( positionBonus * 0.05 )
171+ + ( partialMatchBonus * 0.05 )
172+ )
173+
174+ return Math . min ( score , 1 )
172175 }
173176
174- private isContiguousMatch ( VP : bigint , m : number ) : boolean {
175- return Effect . try ( {
176- try : ( ) => {
177- let first = - 1
178- let last = - 1
179- for ( let i = 0 ; i < m ; i ++ ) {
180- if ( ( VP & ( 1n << BigInt ( i ) ) ) !== 0n ) {
181- if ( first === - 1 )
182- first = i
183- last = i
184- }
185- }
186- return ( last - first + 1 ) === m
187- } ,
188- catch : error => new Error ( `[isContiguousMatch]: Failed to check if match is contiguous: ${ error } ` ) ,
189- } ) . pipe ( Effect . runSync )
177+ private isContiguousMatch ( VP : number [ ] , m : number ) : boolean {
178+ let first = - 1
179+ let last = - 1
180+ for ( let i = 0 ; i < m ; i ++ ) {
181+ const chunkIndex = Math . floor ( i / 32 )
182+ const bitPosition = i % 32
183+ if ( chunkIndex >= VP . length )
184+ break
185+ if ( ( VP [ chunkIndex ] & ( 1 << bitPosition ) ) !== 0 ) {
186+ if ( first === - 1 )
187+ first = i
188+ last = i
189+ }
190+ }
191+ return ( last - first + 1 ) === m && first !== - 1
190192 }
191193
192- private findMatchRanges ( VP : bigint , m : number , endIndex : number ) : [ number , number ] [ ] {
193- return Effect . try ( {
194- try : ( ) => {
195- const ranges : [ number , number ] [ ] = [ ]
196- let start = - 1
197- let end = - 1
198-
199- for ( let i = 0 ; i < m ; i ++ ) {
200- if ( ( VP & ( 1n << BigInt ( i ) ) ) !== 0n ) {
201- if ( start === - 1 )
202- start = endIndex - i
203- end = endIndex - i
204- }
205- else if ( start !== - 1 ) {
206- ranges . push ( [ start , end ] )
207- start = - 1
208- end = - 1
209- }
210- }
194+ private findMatchRanges ( VP : number [ ] , m : number , endIndex : number ) : [ number , number ] [ ] {
195+ const ranges : [ number , number ] [ ] = [ ]
196+ let start = - 1
197+ let end = - 1
198+
199+ for ( let i = 0 ; i < m ; i ++ ) {
200+ const chunkIndex = Math . floor ( i / 32 )
201+ const bitPosition = i % 32
202+ if ( chunkIndex < VP . length && ( VP [ chunkIndex ] & ( 1 << bitPosition ) ) !== 0 ) {
203+ if ( start === - 1 )
204+ start = endIndex - i
205+ end = endIndex - i
206+ }
207+ else if ( start !== - 1 ) {
208+ ranges . push ( [ start , end ] )
209+ start = - 1
210+ end = - 1
211+ }
212+ }
211213
212- if ( start !== - 1 ) {
213- ranges . push ( [ start , end ] )
214- }
214+ if ( start !== - 1 ) {
215+ ranges . push ( [ start , end ] )
216+ }
215217
216- return ranges
217- } ,
218- catch : error => new Error ( `[findMatchRanges]: Failed to find match ranges: ${ error } ` ) ,
219- } ) . pipe ( Effect . runSync )
218+ return ranges
220219 }
221220}
0 commit comments