記一次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, '')
}
複製代碼
相關文章
相關標籤/搜索