记一次bug解决过程(数字转化成中文)

前言

因为公司业务周期较短,时常是几个项目一块儿作,或是加上bug修复。上午的时候接到个任务,正式上的网站发现以下图的错误。 前端

表头错误

问题缘由

找到项目中对应页面,很快发现了问题,原来以前的哥们经过定义了个数组,经过序号来取对应的中文。这样的方式缺点很明显,个数有限。致使如今出现不够用的情况。 正则表达式

解决方式

通过考虑以为能够抽出一个公共的方法代码以下:数组

/**
 * 根据输入的数字返回对应是中文,格式如:三十一
 *  @param {Number} index 序号
 */
export function covertNumberTochinese(index) {
  const multiple = parseInt((index + 1) / 10)
  const remainder = (index + 1) % 10
  const weekheadNum = [
    '一',
    '二',
    '三',
    '四',
    '五',
    '六',
    '七',
    '八',
    '九',
    '十'
  ]
  let text = ''
  const textMap = new Map([
    [/^1_[0-9]$/, () => `十${weekheadNum[remainder - 1]}`],
    [/^1_0$/, () => `十`],
    [
      /^[2-9]_[1-9]$/,
      () => `${weekheadNum[multiple - 1]}${weekheadNum[remainder - 1]}`
    ],
    [/^[2-9]_0$/, () => `${weekheadNum[multiple - 1]}十`],
    [/^0_\w/, () => `${weekheadNum[remainder - 1]}`]
  ])
  const textList = [...textMap].filter(([reg]) => {
    return reg.test(multiple + '_' + remainder)
  })
  textList.forEach(([reg, callBack]) => {
    text = callBack()
  })
  return text
}
复制代码
  1. multiple用来判断有几个10,remainder表示余几。
  2. 这里使用Map数据结构的缘由是由于将对象做为键名,这样能够将正则与函数关联起来。
  3. 使用正则匹配,能够匹配多种状况,使用正则的test方法来校验是否匹配成功。  

 须要用到的知识点

  1. new Map([['键名':'键值']])
  2. ... 扩展运算符 遍历 Iterator 接口(这里是Map)
  3. [reg] = [/^1_[0-9]$/, () => `十${weekheadNum[remainder - 1]}`] 数组结构赋值

分析

  1. covertNumberTochinese函数接受一个数值,对数值进行取余 remainder(用来看成个位数)和商取整的multiple(用来看成十位数)。
  2. 将个位数十位数拼接成用_相连的字符串,而后用正则取匹配这个字符串。而后两个中文字拼接起来。
  3. 须要注意的是当multiple为1,remainder为0时本应该返回'十零'可是咱们习惯不是注意的叫法,多加一个判断条件。
  4. 判断下remainder(个位数)为0时,省去'几十零'后面的零

小结

用上面的方法能够将数字匹配到中午 1-99位,虽然项目中是够用了,可是局限性仍是很大,并且判断的条件不少,逻辑不直观。浏览器

寻求其余解决方案

/**
 * 根据输入的数字返回对应是中文,格式如:一千零一
 *  @param {Number|String} index 序号或者数字开头的字符串
 */
  export numberToChinese(number) {
      number = String(parseInt(number))
      const ChineseText = '零一二三四五六七八九'
      const smallUnit = '十百千'
      const length = number.length
      let n = length - 2
      let string = ''
      for (var i = 0; i < length; i++) {
        let num = number.charAt(i)
        string += ChineseText.charAt(num)
        string += num > 0 ? smallUnit.charAt(n) : ''
        n--
      }
      return string
    }
复制代码

分析

  1. 首先将传入的数字取整处理,而后将其转换成字符串,为了是使用字符串的.charAt方法取对应位数的中文。
  2. 定义好中文的0-9,以及单位'十百千' (为了简单起见,只讨论四位数如下的转换,位数增长方法是相似的)
  3. 定义好string是最终返回的中文字符串,是经过拼接的方式获得一个中文的数字,这里n表明取第几位单位也就是取smallUnit的第几位

循环体中作的事

  1. 去除number(也就是数字字符串如1001)的第一位,就是数字1类型是string类型,而后经过这个1取ChineseText中的中文获得中文的一
  2. 而后经过n取一对应的单位,这里n=length-2是由于再定义单位时是从十开始的而不是从个位数开始,而且.charAt(0)取的是字符串的第一位。而且当数字为零的时候不须要单位,因此加了num > 0的限定条件。

完善

就此数字转换成中文的方法就写完了,在浏览器中打印以下 bash

1001
有多个零的时候咱们习惯是一千零一,而且结尾为零的时候是省略的 因此 咱们还得对返回的string作一下去零处理

/**
 * 根据输入的字符串返回按照规则去零后的字符串,格式如:一千零一
 *  @param {string} str 中文数字 如 一千零零一
 */
export clearZero(str){
    const regMiddle = /零{2}/g
    const regEnd = /零?零$/
    str = str.replace(regMiddle, '零')
    return str.replace(regEnd, '')
}
复制代码
  1. 经过clearZero函数首先将匹配中间有两个零相连的状况,转为一个零
  2. 而后判断结尾是否有一个至两个零的状况,将它们清空
  3. 注意点: replace 不会改变原来的值,须要将操做后的结果从新赋值

将 numberToChinese 函数改成以下数据结构

export numberToChinese(number) {
      number = String(parseInt(number))
      const ChineseText = '零一二三四五六七八九'
      const smallUnit = '十百千'
      const length = number.length
      let n = length - 2
      let string = ''
      for (var i = 0; i < length; i++) {
        let num = number.charAt(i)
        string += ChineseText.charAt(num)
        string += num > 0 ? smallUnit.charAt(n) : ''
        n--
      }
      return  clearZero(string)
    }
复制代码

总结

  1. 四位数如下的数字转换成中文的方法已经完成了 ,更多位数的转换原理相同,有个方法就是将多位数的数字截取成 四位为一组,分组处理,最后将结果拼接起来,这样可能会减小一些逻辑判断。
  2. 核心部分就是经过charAt取数字字符串,而后经过数组去找定义好的ChineseText对应的中文,定义了一个n来取当前位数对应的单位

留言区的意见

首先感谢你们在留言区发表本身宝贵的意见,能抛砖引玉让你们分享本身工做经验 如下代码截取 @皈依佛门小前端在留言区的评论函数

function chin(str){ 
  let cnChar  = "零壹贰叁肆伍陆柒捌玖", 
    partInt = '元拾佰仟万拾佰仟亿拾佰仟', 
    len = str.length-1, 
    arr = new Array((len+1)), 
    i=0; 
  str.replace(/\d/g, n => { 
    let b = partInt.charAt(len-i); 
    arr[i] = cnChar.charAt(n) + (n==='0' && '元万亿'.indexOf(b) < 0 ? '' : b); 
    i++; 
  }); 
  return arr.join('').replace(/(零)\1+/g,'零').replace(/零(十|百|千|万|亿|元)/g, value => value.replace(/零/, '')); 
}
复制代码

接下来分析下以上代码网站

  1. 思路也是定义好数字和单位,经过取好数字以及对应的单位拼接成一个字符串
  2. 介绍下str.replace(regexp, callback=>{ //...}) 第一次参数接受正则规则,匹配成功后将会被替换成第二参数,也就是函数的返回值。该函数的callback值为匹配成功的字符串。
  3. 若是第一个参数是正则表达式, 而且其为全局匹配模式, 那么这个方法将被屡次调用, 每次匹配都会被调用

结果
试着输入111010看下len,arr,return以前的arr各是什么
能够看到 @皈依佛门小前端的思路是:将每项的 数值以及单位做为一项,最后经过 数组join的方法所有拼接在一块儿。而后进行 去零处理

关键代码

let b = partInt.charAt(len-i); 
arr[i] = cnChar.charAt(n) + (n==='0' && '元万亿'.indexOf(b) < 0 ? '' : b)
复制代码
  1. 首先知道cnChar.charAt(n)就是经过数字序号取到的中文数字,而后拼接上b也就是取到的单位。
  2. 在拼接以前作了判断若是是0的状况,判断下是否在个位,万位,和亿位上,不然省略单位,也就是当数字为零时只有这些位上有单位。
  3. 经过正则匹配的方式去掉零。

运行结果

结果
能够看到已经成功将数字转换成中文,可是一十一的读法不符合咱们的习惯

参考意见改进

效果以下: ui

/**
 * 根据输入的数字返回对应是中文,格式如:一千零一
 *  @param {Number|String} index 序号或者数字开头的字符串
 */
  export numberToChinese(number) {
      number = String(parseInt(number))
      const ChineseText = '零一二三四五六七八九'
      const unit = '十百千万十百千亿十百千'
      const length = number.length
      let n = length - 2
      let string = ''
      for (var i = 0; i < length; i++) {
        let num = number.charAt(i)
        let currentUnit = unit.charAt(n)
        string += ChineseText.charAt(num)
        // 经过下面这步至关于将字符串分割成四位一组,由于每四位都会有一个单位,因此零不可能相连超过两个了。
        string +=
          num === '0' && '万亿'.indexOf(currentUnit) < 0 ? '' : currentUnit
        n--
      }
      return this.clearZero(string)
    }
复制代码
/**
 * 根据输入的字符串返回按照规则去零后的字符串,格式如:一千零一
 *  @param {string} str 中文数字 如 一千零零一
 */
 
export clearZero(str){
     const regMiddle = /零{2}/g
      const regEnd = /零?零$/
      const regTen = /一十/g
      str = str.replace(regMiddle, '零')
      str = str.replace(regTen, '十')
      return str.replace(regEnd, '')
}
复制代码
相关文章
相关标签/搜索