@@ -28,21 +28,34 @@ impl FeatureExtractor for CharacterNgrams {
2828 return vec ! [ ] ;
2929 }
3030
31- let mut ngrams = Vec :: new ( ) ;
32- let padding = self . endmarker . repeat ( self . n . saturating_sub ( 1 ) ) ;
31+ // Pre-calculate capacity to avoid reallocations
32+ let text_len = text. chars ( ) . count ( ) ;
33+ let padding_len = self . n . saturating_sub ( 1 ) ;
34+ let total_len = text_len + 2 * padding_len;
3335
34- // Create an iterator that includes padding
35- let padded_text_iter = padding. chars ( ) . chain ( text. chars ( ) ) . chain ( padding. chars ( ) ) ;
36+ if total_len < self . n {
37+ return vec ! [ ] ;
38+ }
39+
40+ let expected_ngrams = total_len - self . n + 1 ;
41+ let mut ngrams = Vec :: with_capacity ( expected_ngrams) ;
42+
43+ let padding = self . endmarker . repeat ( padding_len) ;
3644
37- // Use a buffer to collect characters for each n-gram
38- let mut buffer: Vec < char > = Vec :: with_capacity ( self . n ) ;
45+ // collect chars once, then slice
46+ let mut all_chars = Vec :: with_capacity ( total_len) ;
47+ all_chars. extend ( padding. chars ( ) ) ;
48+ all_chars. extend ( text. chars ( ) ) ;
49+ all_chars. extend ( padding. chars ( ) ) ;
3950
40- for ch in padded_text_iter {
41- buffer. push ( ch) ;
42- if buffer. len ( ) == self . n {
43- ngrams. push ( buffer. iter ( ) . collect :: < String > ( ) ) ;
44- buffer. remove ( 0 ) ;
51+ // Generate n-grams using efficient windowing
52+ for window in all_chars. windows ( self . n ) {
53+ // Pre-allocate string with known capacity
54+ let mut ngram = String :: with_capacity ( self . n * 4 ) ; // Assume max 4 bytes per char
55+ for & ch in window {
56+ ngram. push ( ch) ;
4557 }
58+ ngrams. push ( ngram) ;
4659 }
4760
4861 super :: append_feature_counts ( interner, ngrams)
0 commit comments