@@ -97,14 +97,10 @@ export class HybridSearcher {
9797
9898 let totalLength = 0 ;
9999 for ( const { key, text } of items ) {
100- const tokens = text . toLowerCase ( ) . split ( / \s + / ) . filter ( Boolean ) ;
101- this . docLengths . set ( key , tokens . length ) ;
102- totalLength += tokens . length ;
103-
104- const termFreq = new Map < string , number > ( ) ;
105- for ( const t of tokens ) {
106- termFreq . set ( t , ( termFreq . get ( t ) ?? 0 ) + 1 ) ;
107- }
100+ const termFreq = tokenizeWithFreq ( text ) ;
101+ const docLen = [ ...termFreq . values ( ) ] . reduce ( ( a , b ) => a + b , 0 ) ;
102+ this . docLengths . set ( key , docLen ) ;
103+ totalLength += docLen ;
108104
109105 for ( const [ term , freq ] of termFreq ) {
110106 if ( ! this . invertedIndex . has ( term ) ) {
@@ -134,13 +130,12 @@ export class HybridSearcher {
134130
135131 private updateInvertedEntry ( key : string , text : string ) : void {
136132 this . removeInvertedEntry ( key ) ;
137- const tokens = text . toLowerCase ( ) . split ( / \s + / ) . filter ( Boolean ) ;
138- this . docLengths . set ( key , tokens . length ) ;
133+ const termFreq = tokenizeWithFreq ( text ) ;
134+ const docLen = [ ...termFreq . values ( ) ] . reduce ( ( a , b ) => a + b , 0 ) ;
135+ this . docLengths . set ( key , docLen ) ;
139136 this . totalDocs = this . docLengths . size ;
140137 this . avgDocLength = [ ...this . docLengths . values ( ) ] . reduce ( ( a , b ) => a + b , 0 ) / ( this . totalDocs || 1 ) ;
141138
142- const termFreq = new Map < string , number > ( ) ;
143- for ( const t of tokens ) termFreq . set ( t , ( termFreq . get ( t ) ?? 0 ) + 1 ) ;
144139 for ( const [ term , freq ] of termFreq ) {
145140 if ( ! this . invertedIndex . has ( term ) ) this . invertedIndex . set ( term , new Map ( ) ) ;
146141 this . invertedIndex . get ( term ) ! . set ( key , freq ) ;
@@ -225,8 +220,34 @@ export class HybridSearcher {
225220 }
226221}
227222
223+ const STOP_WORDS = new Set ( [
224+ '的' , '了' , '在' , '是' , '我' , '有' , '和' , '就' , '不' , '人' , '都' , '一' , '一个' ,
225+ '上' , '也' , '很' , '到' , '说' , '要' , '去' , '你' , '会' , '着' , '没有' , '看' , '好' ,
226+ '这' , '那' , '他' , '她' , '它' , '们' , '被' , '把' , '从' , '对' , '与' , '为' , '能' ,
227+ 'the' , 'a' , 'an' , 'is' , 'are' , 'was' , 'were' , 'be' , 'been' , 'being' ,
228+ 'have' , 'has' , 'had' , 'do' , 'does' , 'did' , 'will' , 'would' , 'could' , 'should' ,
229+ 'it' , 'this' , 'that' , 'and' , 'but' , 'or' , 'if' , 'so' ,
230+ 'to' , 'of' , 'in' , 'for' , 'on' , 'with' , 'at' , 'by' , 'from' , 'as' ,
231+ ] ) ;
232+
233+ const segmenter = new Intl . Segmenter ( 'zh-CN' , { granularity : 'word' } ) ;
234+
228235function tokenize ( text : string ) : Set < string > {
229- return new Set ( text . toLowerCase ( ) . split ( / \s + / ) . filter ( Boolean ) ) ;
236+ const tokens = new Set < string > ( ) ;
237+ for ( const { segment, isWordLike } of segmenter . segment ( text . toLowerCase ( ) ) ) {
238+ if ( isWordLike && ! STOP_WORDS . has ( segment ) ) tokens . add ( segment ) ;
239+ }
240+ return tokens ;
241+ }
242+
243+ function tokenizeWithFreq ( text : string ) : Map < string , number > {
244+ const freq = new Map < string , number > ( ) ;
245+ for ( const { segment, isWordLike } of segmenter . segment ( text . toLowerCase ( ) ) ) {
246+ if ( isWordLike && ! STOP_WORDS . has ( segment ) ) {
247+ freq . set ( segment , ( freq . get ( segment ) ?? 0 ) + 1 ) ;
248+ }
249+ }
250+ return freq ;
230251}
231252
232253function calcBM25Score (
0 commit comments