// // KBSuggestionEngine.m // CustomKeyboard // #import "KBSuggestionEngine.h" #import "KBConfig.h" @interface KBSuggestionEngine () @property (nonatomic, copy) NSArray *words; @property (nonatomic, strong) NSMutableDictionary *selectionCounts; @property (nonatomic, strong) NSSet *priorityWords; @property (nonatomic, copy) NSArray *traditionalChineseWords; @property (nonatomic, copy) NSArray *simplifiedChineseWords; @end @implementation KBSuggestionEngine + (instancetype)shared { static KBSuggestionEngine *engine; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ engine = [[KBSuggestionEngine alloc] init]; }); return engine; } - (instancetype)init { if (self = [super init]) { _engineType = KBSuggestionEngineTypeLatin; _selectionCounts = [NSMutableDictionary dictionary]; NSArray *defaults = [self.class kb_defaultWords]; _priorityWords = [NSSet setWithArray:defaults]; _words = [self kb_loadWords]; _traditionalChineseWords = [self kb_loadTraditionalChineseWords]; _simplifiedChineseWords = [self kb_loadSimplifiedChineseWords]; } return self; } - (NSArray *)suggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit { if (prefix.length == 0 || limit == 0) { return @[]; } // 根据引擎类型选择不同的联想逻辑 switch (self.engineType) { case KBSuggestionEngineTypePinyinTraditional: return [self kb_traditionalPinyinSuggestionsForPrefix:prefix limit:limit]; case KBSuggestionEngineTypePinyinSimplified: return [self kb_simplifiedPinyinSuggestionsForPrefix:prefix limit:limit]; case KBSuggestionEngineTypeBopomofo: return [self kb_bopomofoSuggestionsForPrefix:prefix limit:limit]; case KBSuggestionEngineTypeLatin: default: return [self kb_latinSuggestionsForPrefix:prefix limit:limit]; } } - (void)recordSelection:(NSString *)word { if (word.length == 0) { return; } NSString *key = word.lowercaseString; NSInteger count = self.selectionCounts[key].integerValue + 1; self.selectionCounts[key] = @(count); } #pragma mark - Defaults - (NSArray *)kb_loadWords { NSMutableOrderedSet *set = [[NSMutableOrderedSet alloc] init]; [set addObjectsFromArray:[self.class kb_defaultWords]]; NSArray *paths = [self kb_wordListPaths]; for (NSString *path in paths) { if (path.length == 0) { continue; } NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; if (content.length == 0) { continue; } NSArray *lines = [content componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; for (NSString *line in lines) { NSString *word = [self kb_sanitizedWordFromLine:line]; if (word.length == 0) { continue; } [set addObject:word]; } } NSArray *result = set.array ?: @[]; return result; } - (NSArray *)kb_wordListPaths { NSMutableArray *paths = [NSMutableArray array]; // 1) App Group override (allows server-downloaded large list). NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:AppGroup]; if (containerURL.path.length > 0) { NSString *groupPath = [[containerURL path] stringByAppendingPathComponent:@"kb_words.txt"]; [paths addObject:groupPath]; } // 2) Bundle fallback. NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"kb_words" ofType:@"txt"]; if (bundlePath.length > 0) { [paths addObject:bundlePath]; } return paths; } - (NSString *)kb_sanitizedWordFromLine:(NSString *)line { NSString *trimmed = [[line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] lowercaseString]; if (trimmed.length == 0) { return @""; } static NSCharacterSet *letters = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ letters = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyz"]; }); for (NSUInteger i = 0; i < trimmed.length; i++) { if (![letters characterIsMember:[trimmed characterAtIndex:i]]) { return @""; } } return trimmed; } + (NSArray *)kb_defaultWords { return @[ @"a", @"an", @"and", @"are", @"as", @"at", @"app", @"ap", @"apple", @"apply", @"april", @"application", @"about", @"above", @"after", @"again", @"against", @"all", @"am", @"among", @"amount", @"any", @"around", @"be", @"because", @"been", @"before", @"being", @"below", @"best", @"between", @"both", @"but", @"by", @"can", @"could", @"come", @"common", @"case", @"do", @"does", @"down", @"day", @"each", @"early", @"end", @"even", @"every", @"for", @"from", @"first", @"found", @"free", @"get", @"good", @"great", @"go", @"have", @"has", @"had", @"help", @"how", @"in", @"is", @"it", @"if", @"into", @"just", @"keep", @"kind", @"know", @"like", @"look", @"long", @"last", @"make", @"more", @"most", @"my", @"new", @"no", @"not", @"now", @"of", @"on", @"one", @"or", @"other", @"our", @"out", @"people", @"place", @"please", @"quick", @"quite", @"right", @"read", @"real", @"see", @"say", @"some", @"such", @"so", @"the", @"to", @"this", @"that", @"them", @"then", @"there", @"they", @"these", @"time", @"use", @"up", @"under", @"very", @"we", @"with", @"what", @"when", @"where", @"who", @"why", @"will", @"would", @"you", @"your" ]; } #pragma mark - Engine Type Management - (void)setEngineTypeFromString:(NSString *)engineTypeString { if ([engineTypeString isEqualToString:@"latin"]) { self.engineType = KBSuggestionEngineTypeLatin; } else if ([engineTypeString isEqualToString:@"pinyin_traditional"]) { self.engineType = KBSuggestionEngineTypePinyinTraditional; } else if ([engineTypeString isEqualToString:@"pinyin_simplified"]) { self.engineType = KBSuggestionEngineTypePinyinSimplified; } else if ([engineTypeString isEqualToString:@"bopomofo"]) { self.engineType = KBSuggestionEngineTypeBopomofo; } else { self.engineType = KBSuggestionEngineTypeLatin; } NSLog(@"[KBSuggestionEngine] Engine type set to: %@", engineTypeString); } #pragma mark - Latin Suggestions - (NSArray *)kb_latinSuggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit { NSString *lower = prefix.lowercaseString; NSMutableArray *matches = [NSMutableArray array]; for (NSString *word in self.words) { if ([word hasPrefix:lower]) { [matches addObject:word]; if (matches.count >= limit * 3) { break; } } } if (matches.count == 0) { return @[]; } [matches sortUsingComparator:^NSComparisonResult(NSString *a, NSString *b) { NSInteger ca = self.selectionCounts[a].integerValue; NSInteger cb = self.selectionCounts[b].integerValue; if (ca != cb) { return (cb > ca) ? NSOrderedAscending : NSOrderedDescending; } BOOL pa = [self.priorityWords containsObject:a]; BOOL pb = [self.priorityWords containsObject:b]; if (pa != pb) { return pa ? NSOrderedAscending : NSOrderedDescending; } return [a compare:b]; }]; if (matches.count > limit) { return [matches subarrayWithRange:NSMakeRange(0, limit)]; } return matches.copy; } #pragma mark - Traditional Chinese Pinyin Suggestions - (NSArray *)kb_traditionalPinyinSuggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit { // 繁体拼音联想:输入拼音,返回繁体中文候选词 NSString *lower = prefix.lowercaseString; NSMutableArray *matches = [NSMutableArray array]; // 这里应该使用拼音到繁体字的映射表 // 目前先返回一些常用繁体词作为示例 for (NSString *word in self.traditionalChineseWords) { // TODO: 实现拼音匹配逻辑 // 这里需要一个拼音库来将输入的拼音转换为繁体字 [matches addObject:word]; if (matches.count >= limit) { break; } } return matches.copy; } #pragma mark - Simplified Chinese Pinyin Suggestions - (NSArray *)kb_simplifiedPinyinSuggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit { // 简体拼音联想:输入拼音,返回简体中文候选词 NSString *lower = prefix.lowercaseString; NSMutableArray *matches = [NSMutableArray array]; // 这里应该使用拼音到简体字的映射表 for (NSString *word in self.simplifiedChineseWords) { // TODO: 实现拼音匹配逻辑 [matches addObject:word]; if (matches.count >= limit) { break; } } return matches.copy; } #pragma mark - Bopomofo (Zhuyin) Suggestions - (NSArray *)kb_bopomofoSuggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit { // 注音联想:输入注音符号,返回繁体中文候选词 NSMutableArray *matches = [NSMutableArray array]; // 这里应该使用注音到繁体字的映射表 // 注音符号:ㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙ // 韵母:ㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩ // 声调:ˊˇˋ˙ for (NSString *word in self.traditionalChineseWords) { // TODO: 实现注音匹配逻辑 [matches addObject:word]; if (matches.count >= limit) { break; } } return matches.copy; } #pragma mark - Chinese Word Loading - (NSArray *)kb_loadTraditionalChineseWords { // 加载繁体中文常用词 // 这里先返回一些示例词,实际应该从文件或数据库加载 return @[ @"你好", @"謝謝", @"對不起", @"再見", @"早安", @"晚安", @"請問", @"不好意思", @"沒關係", @"加油", @"台灣", @"台北", @"高雄", @"台中", @"台南", @"朋友", @"家人", @"工作", @"學習", @"生活", @"時間", @"地點", @"方法", @"問題", @"答案", @"喜歡", @"愛", @"想念", @"開心", @"快樂", @"美麗", @"漂亮", @"帥氣", @"可愛", @"溫柔" ]; } - (NSArray *)kb_loadSimplifiedChineseWords { // 加载简体中文常用词 return @[ @"你好", @"谢谢", @"对不起", @"再见", @"早安", @"晚安", @"请问", @"不好意思", @"没关系", @"加油", @"中国", @"北京", @"上海", @"广州", @"深圳", @"朋友", @"家人", @"工作", @"学习", @"生活", @"时间", @"地点", @"方法", @"问题", @"答案", @"喜欢", @"爱", @"想念", @"开心", @"快乐", @"美丽", @"漂亮", @"帅气", @"可爱", @"温柔" ]; } @end