最近项目作语音识别下单的功能,可是数字识别出来是以中文汉字的形式展现的,可是输入框是要展现阿拉伯数字的样式传给服务器。git
怎么实现中文数字转阿拉伯数字呢?咱们来捋一捋,思路是这样的无非两个大的步骤: 1.从一长串字符串中提取中文数字:零一二三四五六七八九十百千万亿; 2.将提取的中文数字字符转换成阿拉伯数字,而后组合起来,且要等价于以前的中文数字大小。算法
首先咱们定义两个字典,以键值对的形式存起来,key为中文,value为数字。第一个字典是零到九的十进制中-阿数字。第二字典的每一个元素能够看作是第一个字典元素的单位,其对应的值也是个字典,value是单位对应的大小权数,secUnit是个bool值其含义是是否为万或者亿。至于为何要区分万、亿?是由于一般数字咱们都是4位一分割,万、亿是个特殊节点,看后面的算法你们可能就会明白。数组
NSDictionary *chnNumChar = @{@"零":@0,@"一":@1,@"二":@2,@"两":@2,@"三":@3,@"四":@4,@"五":@5,@"六":@6,@"七":@7,@"八":@8,@"九":@9}; NSDictionary *chnNameValue = @{ @"十":@{@"value":@10, @"secUnit":@(false)}, @"百":@{@"value":@100, @"secUnit":@(false)}, @"千":@{@"value":@1000, @"secUnit":@(false)}, @"万":@{@"value":@10000, @"secUnit":@(true)}, @"亿":@{@"value":@100000000, @"secUnit":@(true)}, };
咱们先分析上面的第2点如何传入中文提取到的中文中文字符串转数字,至于如何提取到中文数字咱们后面讲,这个比较简单。既然咱们上面定义了字典,很显然咱们要拿到单个字符,确定是要遍历字符串的。服务器
//遍历字符串,单个字符存入数组 NSMutableArray *strArrM = [NSMutableArray array]; for (NSInteger i = 0;i < chnStr.length;i ++) { NSString *charStr = [chnStr substringWithRange:NSMakeRange(i, 1)]; //此处字符筛选下比较好,由于某些缘由可能存在非数字字符 if (chnNumChar[charStr] != nil || chnNameValue[charStr] != nil) { [strArrM addObject:charStr]; } }
拿到单个字符后,咱们要作的就是将数组遍历,而后计算字符转换后计算的总值。大概思路是若是是取到key对应的值,若是是十进制数字就用变量number存起来,后面确定会接一个单位数值,若是是10、百、千就拿前面的number和其对应的权数相乘用section接收,在没遇到万、亿以前累加section += (number * unit);当字符为万、亿就拿以前累加的和乘以权数,并存储在变量rtn += section;此时section置零开启下次循环。每次循环number应置零。若万、亿后再无如何字符,rtn即是最终结果,如有应加上section的计算值。笔者中文表述水平有限,直接上代码,你们可能看的比较清楚,作了必要注释:测试
NSInteger number = 0;//用以接收下面循环遍历的数字字符的数值 NSInteger section = 0;//用以万、亿字符节点前求和 NSInteger rtn = 0;//若万、亿后再无如何字符,rtn即是最终结果,如有应加上section的计算值 BOOL secUnit = false;//字符是否为万、亿 //遍历字符数组 for(NSInteger i = 0; i < strArrM.count; i++){ //取出中文数字字符 NSNumber *temNum = chnNumChar[strArrM[i]]; NSInteger num = [temNum integerValue]; if(temNum != nil){ //中文数字字符 number = num; //若为最后一个字符直接相加 if(i == strArrM.count - 1){ section += number; } }else{ //中文单位字符 //取出单位字符对应的数值 NSNumber *temUnit = chnNameValue[strArrM[i]][@"value"]; NSInteger unit = [temUnit integerValue]; //取出单位字符对应的类型 NSNumber *temSecUnit = chnNameValue[strArrM[i]][@"secUnit"]; secUnit = [temSecUnit boolValue]; if(!secUnit){ //单位为10、百、千,拿数字值乘以单位数值,并累加 section += (number * unit); }else{ //单位为万、亿,拿前面的数值累加的结果乘以对应的单位数值 section = (section + number) * unit; //累加 rtn += section; //用后置零 section = 0; } //用后置零 number = 0; } } NSLog(@"中文数字:%@,阿拉伯数字:%ld",chnStr,rtn + section);
如此咱们就完成了汉字转阿拉伯数字。code
在后面的实测中发现一个问题,若是咱们说十,十万,十亿等“十”前面没有十进制字符或者单纯“十”前面只有一个“零”的时候咱们转换的结果是当前节点的值为0。经过观察前面的算法不难发现十做为单位字符前面确定要跟数字字符系数的否则系数就至关于0,零十至关于0 * 10。因此咱们应该判断十所在位置的前一个字符的值,前面没有“一”到“九”或者前面是个零的应该在“十”前面追加个“一”存入数组:component
//遍历字符串用数组接收单个字符 NSMutableArray *strArrM = [NSMutableArray array]; for (NSInteger i = 0;i < chnStr.length;i ++) { NSString *charStr = [chnStr substringWithRange:NSMakeRange(i, 1)]; if (chnNumChar[charStr] != nil || chnNameValue[charStr] != nil) { NSString *tempStr = [chnStr substringWithRange:NSMakeRange(i - 1, 1)]; //若是第一个字符为十则在其前面添‘一’;若是字符“十”的前一个字符为“零”或者前面没有数字字符则在其前面添“一” if (i == 0 && [charStr isEqual:@"十"]) { [strArrM addObject:@"一"]; }else if( i > 0 && (chnNumChar[tempStr] == nil || [tempStr isEqual:@"零"]) && [charStr isEqual:@"十"]){ [strArrM addObject:@"一"]; } [strArrM addObject:charStr]; } }
中文数字转阿拉伯数字相信你们应该都明白了,回到前面咱们如何把普通汉字和数字字符分开呢?咱们分析下,一句话中可能存在中文英文数字和标点,为了便于转换咱们先把标点去除,获得无标点的字符后再遍历,不是数字字符的就用数组cnArr存起来,遇到是数字字符的时候咱们也把它用数组digitalArr存起来,若是这个非数字字符前一个字符是数字字符,咱们就把以前的digitalArr,cnArr用componentsJoinedByString方法分别合并成字符串,存储在数组中。orm
//正则匹配标点 NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"@//:;()¥「」"、【】;“[]{}#%-*+=_\\|~<>$€^•'@#$%^&*()_+'\",,.。??"]; //去除标点合并 NSString *trimmedString = [[str componentsSeparatedByCharactersInSet:set] componentsJoinedByString: @""]; NSLog(@"%@",trimmedString); //此字典增长了阿拉伯数字,由于一句话中可能也存在不是中文数字的咱们只须要挑出来,并不须要再去转阿拉伯数字了 NSDictionary *chnNumChar = @{@"零":@0,@"一":@1,@"二":@2,@"两":@2,@"三":@3,@"四":@4,@"五":@5,@"六":@6,@"七":@7,@"八":@8,@"九":@9,@"0":@0,@"1":@1,@"2":@2,@"3":@3,@"4":@4,@"5":@5,@"6":@6,@"7":@7,@"8":@8,@"9":@9}; NSDictionary *chnNameValue = @{ @"十":@{@"value":@10, @"secUnit":@(false)}, @"百":@{@"value":@100, @"secUnit":@(false)}, @"千":@{@"value":@1000, @"secUnit":@(false)}, @"万":@{@"value":@10000, @"secUnit":@(true)}, @"亿":@{@"value":@100000000, @"secUnit":@(true)}, }; NSMutableArray *allDic = [NSMutableArray array];//汉字对应的数字数组 NSMutableArray *cnArr = [NSMutableArray array];//汉字数组 NSMutableArray *digitalArr = [NSMutableArray array];// 数字数组 BOOL isNum = NO;//是不是数字 for (NSInteger i = 0; i < trimmedString.length; i ++) { NSString *charStr = [trimmedString substringWithRange:NSMakeRange(i, 1)]; if (chnNumChar[charStr] != nil || chnNameValue[charStr] != nil) { isNum = YES; [digitalArr addObject:charStr]; if (i == trimmedString.length - 1) { [allDic addObject:@[[cnArr componentsJoinedByString:@""],[digitalArr componentsJoinedByString:@""]]]; [cnArr removeAllObjects]; [digitalArr removeAllObjects]; } }else{ if (i > 0 && isNum == YES) { [allDic addObject:@[[cnArr componentsJoinedByString:@""],[digitalArr componentsJoinedByString:@""]]]; [cnArr removeAllObjects]; [digitalArr removeAllObjects]; isNum = NO; } [cnArr addObject:charStr]; } } NSString *endStr = @"\n"; for (NSArray *valu in allDic) { NSInteger number = 0; if ([valu[1] integerValue] > 0) { number = [valu[1] integerValue]; }else{ //chineseNumbersReturnArabicNumerals方法为最上面提到自定义的中文数字转阿拉伯数字 number = [self chineseNumbersReturnArabicNumerals:valu[1]]; } endStr = [endStr stringByAppendingString:[NSString stringWithFormat:@"%@ = %ld;\n",valu[0],number]]; } NSLog(@"%@",endStr); /*以@"件数十,毛重一千..。计费重量一千."为例遍历打印的结果就是: 件数 = 10; 毛重 = 1000; 计费重量 =1000; */
========= 分割线 (2018.4.24更新) =========blog
上面那种算法是从高位取到低位,下面提供一种从低位到高位取值转换的方法,感兴趣的同窗能够看下,已作详细注释。算法我只测试了一些比较有表明性的数据,可能存在不严谨局限的地方,如要接入项目请斟酌:rem
//测试数据 chnStr = @"十一亿一千一百一十一万一千一百一十一"; NSDictionary *chnNumChar = @{@"零":@0,@"一":@1,@"二":@2,@"两":@2,@"三":@3,@"四":@4,@"五":@5,@"六":@6,@"七":@7,@"八":@8,@"九":@9,@"十":@10,@"百":@100,@"千":@1000,@"万":@10000,@"亿":@100000000}; //遍历字符串用数组接收单个字符 NSMutableArray *strArrM = [NSMutableArray array]; for (NSInteger i = 0;i < chnStr.length;i ++) { NSString *charStr = [chnStr substringWithRange:NSMakeRange(i, 1)]; if (chnNumChar[charStr] != nil) { NSString *tempStr = [chnStr substringWithRange:NSMakeRange(i - 1, 1)]; //若是第一个字符为十则在其前面添‘一’;若是字符“十”的前一个字符为“零”或者前面没有数字字符则在其前面添“一” if (i == 0 && [charStr isEqual:@"十"]) { [strArrM addObject:@"一"]; }else if( i > 0 && (chnNumChar[tempStr] == nil || [tempStr isEqual:@"零"]) && [charStr isEqual:@"十"]){ [strArrM addObject:@"一"]; } [strArrM addObject:charStr]; } } NSArray *arr = [[strArrM reverseObjectEnumerator] allObjects];//数组倒序 NSInteger total = 0;//总值 NSInteger r = 1;//位权 NSInteger u = 1;//记录单位节点 for (NSInteger i = 0; i < arr.count; i ++) { NSInteger val = [chnNumChar[arr[i]] integerValue];//从右至左(从低位到高位)逐位取值 ←---- if (val >= 10){ //单位字符 if (val > r) { //若是此时的字符单位值大于以前的位权 //把单位值赋值给位权r,并记录此时的最大单位u r = val; u = val; }else{ //若是此时的字符单位值不大于以前的位权 //此前的最大单位u与此时的字符单位的乘积即为此时的位权 r = u * val; } }else{ //数字字符 //累加计算当前的总值 total += r * val; //NSLog(@"%ld",total); } } NSLog(@"\n====\n 中文数字: %@ ;\n 阿拉伯数字:%ld 。\n====",chnStr,total);
文件放在码云,调用相应类方法即可使用。 有什么不足的地方欢迎探讨指正,谢谢。