前言
由於公司業務週期較短,時常是幾個專案一起做,或是加上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
}
複製程式碼
- multiple用來判斷有幾個10,remainder表示餘幾。
- 這裡使用Map資料結構的原因是因為將物件作為鍵名,這樣可以將正則與函式關聯起來。
- 使用正則匹配,可以匹配多種情況,使用正則的test方法來校驗是否匹配成功。
需要用到的知識點
- new Map([['鍵名':'鍵值']])
- ... 擴充套件運算子 遍歷 Iterator 介面(這裡是Map)
- [reg] = [/^1_[0-9]$/, () => `十${weekheadNum[remainder - 1]}`] 陣列結構賦值
分析
- covertNumberTochinese函式接受一個數值,對數值進行取餘 remainder(用來當作個位數)和商取整的multiple(用來當作十位數)。
- 將個位數十位數拼接成用_相連的字串,然後用正則取匹配這個字串。然後兩個中文字拼接起來。
- 需要注意的是當multiple為1,remainder為0時本應該返回'十零'但是我們習慣不是注意的叫法,多加一個判斷條件。
- 判斷下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
}
複製程式碼
分析
- 首先將傳入的數字取整處理,然後將其轉換成字串,為了是使用字串的
.charAt
方法取對應位數的中文。 - 定義好中文的0-9,以及單位'十百千' (為了簡單起見,只討論
四位數以下
的轉換,位數增加方法是類似的) - 定義好string是最終返回的中文字串,是通過
拼接
的方式得到一箇中文的數字,這裡n代表取第幾位單位也就是取smallUnit
的第幾位
迴圈體中做的事
- 去除number(也就是數字字串如1001)的第一位,就是數字1型別是string型別,然後通過這個1取
ChineseText
中的中文得到中文的一 - 然後通過n取一對應的單位,這裡n=length-2是因為再定義單位時是
從十開始
的而不是從個位數開始,並且.charAt(0)取的是字串的第一位
。並且當數字為零的時候不需要單位,所以加了num > 0的限定條件。
完善
就此數字轉換成中文的方法就寫完了,在瀏覽器中列印如下
有多個零的時候我們習慣是一千零一,並且結尾為零的時候是省略的 所以 我們還得對返回的string做一下去零處理/**
* 根據輸入的字串返回按照規則去零後的字串,格式如:一千零一
* @param {string} str 中文數字 如 一千零零一
*/
export clearZero(str){
const regMiddle = /零{2}/g
const regEnd = /零?零$/
str = str.replace(regMiddle, '零')
return str.replace(regEnd, '')
}
複製程式碼
- 通過clearZero函式首先將匹配中間有兩個零相連的情況,轉為一個零
- 然後判斷結尾是否有一個至兩個零的情況,將它們清空
- 注意點: 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)
}
複製程式碼
總結
- 四位數以下的數字轉換成中文的方法已經完成了 ,更多位數的轉換原理相同,有個方法就是將多位數的數字擷取成 四位為一組,分組處理,最後將結果拼接起來,這樣可能會減少一些邏輯判斷。
- 核心部分就是通過
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(/零/, ''));
}
複製程式碼
接下來分析下以上程式碼
- 思路也是定義好數字和單位,通過取好數字以及對應的單位拼接成一個字串
- 介紹下str.replace(regexp, callback=>{ //...}) 第一次引數接受正則規則,匹配成功後將會被替換成第二引數,也就是函式的返回值。該函式的callback值為匹配成功的字串。
- 如果第一個引數是正規表示式, 並且其為全域性匹配模式, 那麼這個方法將被多次呼叫, 每次匹配都會被呼叫
可以看到 @皈依佛門小前端的思路是:將每項的
數值以及單位
作為一項,最後通過陣列join
的方法全部拼接在一起。然後進行去零處理
。
關鍵程式碼
let b = partInt.charAt(len-i);
arr[i] = cnChar.charAt(n) + (n==='0' && '元萬億'.indexOf(b) < 0 ? '' : b)
複製程式碼
- 首先知道cnChar.charAt(n)就是通過數字序號取到的中文數字,然後拼接上b也就是取到的單位。
- 在拼接之前做了判斷如果是0的情況,判斷下是否在個位,萬位,和億位上,否則省略單位,也就是當數字為零時只有這些位上有單位。
- 通過正則匹配的方式去掉零。
執行結果
可以看到已經成功將數字轉換成中文,但是一十一的讀法不符合我們的習慣參考意見改進
效果如下:
/**
* 根據輸入的數字返回對應是中文,格式如:一千零一
* @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, '')
}
複製程式碼