通過正則高效實現千位符(一行程式碼你敢信?)

大雄沒了哆啦A夢發表於2018-08-17

怎麼實現千位符

將數字轉為可讀性比較高的具有千位符是筆試/面試經常被問到的,實現方案也有很多種。 一般情況下,按照我們普通的想法,就是將數字轉為字串,然後使用字串的substr、slice、substring來實現,從右到左,每隔三位插入一個",",eg:

var readableNumber = function(number) {
   if(!Number(number)) {
      throw TypeError('arugment must be number or can be transfer into number');
    }
    var numberStr = '' + number,
      len = numberStr.length,
      index = - 3;

    // 第一步:最高位不足3的情況,用0補上
    switch(len % 3) {
      case 1: numberStr = '00' + numberStr; break;
      case 2: numberStr = '0' + numberStr; break;
    }
    var result = "";
    len = numberStr.length;
    // 第二步:從右往做,每隔三個位置打一個","
    while(-index <= len) {
      result =  numberStr.substr(index, 3) + ',' + result;
      index -= 3;
    }
    // 第三步:將第一步在前面新增的0去掉以及尾部多於的","也去掉
    return result.replace(/(^0+|,$)/g, '');
}
複製程式碼

當然還可以數字轉為陣列,然後迴圈陣列,每隔三個元素插入一個",",這種方式和第一種方式其實都是差不多的思路,通過迴圈來實現。

還有另外的思路,就是使用正規表示式匹配,將每隔三個數字替替換成三個數字+","的方式,如下:

var readableNumber = function(number) {
    if(!Number(number)) {
      throw TypeError('arugment must be number or can be transfer into number');
    }
    var numberStr = '' + number,
      len = numberStr.length
    // 第一步:最高位不足3的情況,用0補上
    switch(len % 3) {
      case 1: numberStr = '00' + numberStr; break;
      case 2: numberStr = '0' + numberStr; break;
    }
    // 第二步:每隔三個數字新增一個",",並將頭部多於的0和尾部多於的","去掉
    return numberStr.replace(/(\d{3})/g, '$1,').replace(/(^0+)/g, '');
  }

複製程式碼

兩種方式,顯然用正規表示式效率會高點。第一種方式時間複雜度是O(n),第二種....(不知道怎麼算正則的時間複雜度),但本人對比了下,千萬級的數字,第一種方式要花費500-600毫秒,第二種方式100-200毫秒。

到這裡就結束了?不不不,還有一種更加簡潔的方式,有人是這樣寫的,體驗下:

vartoThousands = function(number) {
    return (number + '').replace(/(\d)(?=(\d{3})+$)/g, '$1,');
}
複製程式碼

溜不溜☺☺☺?是不是看不懂啊~我也看不懂,感受到正則的牛逼之處,但好事多磨,我們來慢慢研究下。

首先,這個用到的是正向預查的正規表示式(當然還有反向預查),那什麼是正向預查呢?我來講下

正則相關

正向預查

?=就是正向預查的正規表示式,所謂正向,是因為它匹配的是?=表示式左邊的表示式。比如有<表示式1>(?=<表示式2>),那麼它只能匹配到<表示式2>左邊的符合<表示式1>的字串。看下面例子

/\d+(?=%)/.test('50%') // 匹配出50,這個表示式是要提取出百分數字,RegExp.$1=50

複製程式碼

不知道說得明白不~~~

反向預查

?<=是反向預查的正規表示式,和正向相反,它匹配的是?<=表示式右邊的表示式。

/(?<=\$)(\d+)/.test("$90") // 匹配出90,這個表示式是要提取金額數字,RegExp.$1=90
複製程式碼

這裡講一下,我曾經做過一個需求,就是使用者輸入框只能輸入“金錢”,把非金錢的字元過濾掉,我就用到的反向預查。

  // 0開頭.開頭或者非數字的都去掉
 let value = target.value.replace(/^0|^\.|[^\d.]/g, '')
 // 將多餘的.去掉,會將12.36.3254...過濾為12.36
 value = value.replace(/(?<=\d+\.(\d+)?)(\.+\d*)*/g, '')
 target.value = value
複製程式碼

好啦,那麼回到正題,針對/(\d)(?=(\d{3})+$)/g,我們來講解下

根據正向預查,/(\d)(?=(\d{3})+)/就是匹配符合右邊有3的倍數個數字以上的左邊一個數字的字串,它能把11231234替換成1,1231234。

這明顯不是我們要的結果,我們只想要右邊是3的倍數個,而不是3的倍數個以上,所以我們加上$,變成/(\d)(?=(\d{3})+$)/,這個能把11231234替換成11,231234,這個結果我們比起上一步更接近我們想要的了,但右邊的還沒替換完成。

所以接下來我們以同樣的匹配規則繼續匹配右邊的231234。 繼續往右邊匹配我們就想到了global修飾符,於是有/(\d)(?=(\d{3})+$)/g,最後就是我們要的結果了。順便講下global修鎖符

global修飾符

g的作用,就是匹配完第一次後,繼續匹配剩下的。

var reg = /(\d)(?=(\d{3})+$)/g
// 剛開始reg.lastIndex = 0,即從0號位置開始匹配
reg.test('1234567') // 匹配“1”,且lastIndex繼續往前面走,因為匹配到1只有1位,即reg.lastIndex = 1,下次從1號位置開始匹配
reg.test('1234567') // 匹配“234”,且lastIndex繼續往前面走,因為匹配了234,所以走3位,來到了4,下次從4號位置開始匹配
reg.test('1234567') // 匹配到456,lastIndex繼續往前走,走3位,來到了7,7大於總位數,所以結束
複製程式碼

可能講得不夠清晰,有錯誤的地方請指正。

補充,根據各位大佬的回覆,還有一種方式:

Number("1234564").toLocaleString()
複製程式碼

相關文章