解決的問題
很多有關于通訊錄的功能都會有用戶搜索數據庫,從數據庫中模糊查詢出用戶所需要的信息這樣的需求。當需求為可以通過名稱、首字母、全拼進行查詢時,設計的數據庫如下:
姓名 | 首字母 | 全拼 |
---|---|---|
天真無邪 | tzwx | tianzhenwuxie |
查詢的流程圖
但是用著用著就發現了一些查詢結果不準確的情況
比如:
用戶輸入:nc
實際想搜索:內存、農村...
數據庫保存的漢字對應的拼音:biancheng
搜索結果:除了本來想通過首字母查詢出來的正確匹配項以外,額外會搜索出來“編程”這個匹配項。
以及
用戶輸入:henqiang
實際想搜索:很強
數據庫保存的漢字對應拼音:chenqiang
搜索結果:除了本來想通過首字母查詢出來的正確匹配項以外,額外會搜索出來“陳強”這個匹配項。
解決方案
在數據庫存儲的全拼中插入空格,如:
姓名 | 首字母 | 全拼 |
---|---|---|
天真無邪 | tzwx | (字符串頭部也要有空格)tian zhen wu xie |
且為用戶輸入的字符串同樣的加上空格,這樣處理之后就可以避免了上面可能出現的問題。
但是在為用戶輸入字符串中加入空格的時候,也同樣的遇到了問題,比如:
用戶輸入:xian
用戶可能會有4種想要搜索的內容
姓名 | 全拼 |
---|---|
閑 | xian |
西安 | xi an |
俠女 | xia nv |
希阿娜 | xi a na |
為了解決上述的問題,可以寫一個字典樹,去獲取用戶輸入的所有的查詢可能性。
字典樹
網絡上已經有很多字典樹實現的原理了,那么這邊就直接上代碼
節點
@interface RCYTrieTreeNode : NSObject
//是否可以成為結束的節點
@property (nonatomic, assign) BOOL canBeEnd;
//子節點
@property (nonatomic, strong) NSMutableDictionary *children;
@end
樹
插入方法
- (void)insertNodeWithWord:(NSString *)word {
NSMutableArray *words = [[NSMutableArray alloc] init];
for (int i = 0; i < word.length; i++) {
[words addObject:[word substringWithRange:NSMakeRange(i, 1)]];
}
__block RCYTrieTreeNode *node = self.rootNode;
[words enumerateObjectsUsingBlock:^(NSString *ch, NSUInteger idx, BOOL * _Nonnull stop) {
if (!node.children) {
node.children = [[NSMutableDictionary alloc] init];
}
if ([node.children.allKeys containsObject:ch]) { //key中是否有該字符
if (idx == words.count - 1) { //如果是最后一個
RCYTrieTreeNode *endNode = [node.children valueForKey:ch];
endNode.canBeEnd = YES;
}
}
else {
RCYTrieTreeNode *newNode = [[RCYTrieTreeNode alloc] init];
if (idx == words.count - 1) {
newNode.canBeEnd = YES;
}
[node.children setValue:newNode forKey:ch];
}
node = [node.children valueForKey:ch];
}];
}
查找方法
- (RCYTrieTreeNode *)searchTreeWithWord:(NSString *)word {
NSMutableArray *words = [[NSMutableArray alloc] init];
for (int i = 0; i < word.length; i++) {
[words addObject:[word substringWithRange:NSMakeRange(i, 1)]];
}
__block RCYTrieTreeNode *node = self.rootNode;
[words enumerateObjectsUsingBlock:^(NSString *ch, NSUInteger idx, BOOL * _Nonnull stop) {
if ([node.children.allKeys containsObject:ch]) {
node = [node.children valueForKey:ch];
}
else {
node = nil;
*stop = YES;
return;
}
}];
return node;
}
通過字典樹獲得劃分好的數組
切分拼音
+ (void)splitPinYinWithString:(NSString *)string successBlock:(void (^)(NSArray *))successBlock {
RCYTrieTree *tree = [[RCYTrieTree alloc] init];
NSMutableArray *resultArray = [[NSMutableArray alloc] init];
[self PinYinAddSpaceWithTree:tree string:string index:0 resultArray:resultArray];
if (resultArray && successBlock) {
successBlock(resultArray);
}
}
//例如:xianguo -> xian guo, xian gu o, xi an guo, xi an gu o
+ (void)PinYinAddSpaceWithTree:(RCYTrieTree *)tree string:(NSString *)string index:(NSInteger)index resultArray:(NSMutableArray *)resultArray {
NSInteger currentLength = 1;
BOOL isFind = YES;
NSMutableString *resultString = [NSMutableString stringWithString:string];
while (currentLength + index <= string.length) {
NSString *subString = [string substringWithRange:NSMakeRange(index, currentLength)];
RCYTrieTreeNode *node = [tree searchTreeWithWord:subString];
if (!node) {
isFind = NO;
break;
}
if (node.canBeEnd) {
//遞歸
//一旦找到后分為兩個方法,一個為加入空格繼續查找,一個為不加空格繼續查找 如:字符串xian 查到xi 的時候 一個方法的字符串為 xi an 另一個為 xian
if (index + currentLength != resultString.length) {
NSMutableString *mutableString = [NSMutableString stringWithString:string];
[mutableString insertString:@" " atIndex:currentLength + index];
[self PinYinAddSpaceWithTree:tree string:mutableString index:currentLength + index + 1 resultArray:resultArray];
}
}
currentLength++;
}
if (isFind) {
[resultArray addObject:resultString];
}
}
輸入xian
輸出結果為:
RCYTrieTree[51502:1416638] (
"xi a n",
"xi an",
"xia n",
xian
)
最后用獲取到的數組去數據庫里面like查詢就好啦。
代碼地址:RCYTrieTree