阅读 2549

通过正则高效实现千位符(一行代码你敢信?)

怎么实现千位符

将数字转为可读性比较高的具有千位符是笔试/面试经常被问到的,实现方案也有很多种。 一般情况下,按照我们普通的想法,就是将数字转为字符串,然后使用字符串的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号位置开始匹配
res.test('1234567') // 匹配“1”,且lastIndex继续往前面走,因为匹配到1只有1位,即reg.lastIndex = 1,下次从1号位置开始匹配
res.test('1234567') // 匹配“234”,且lastIndex继续往前面走,因为匹配了234,所以走3位,来到了4,下次从4号位置开始匹配
res.test('1234567') // 匹配到456,lastIndex继续往前走,走3位,来到了7,7大于总位数,所以结束
复制代码

可能讲得不够清晰,有错误的地方请指正。

补充,根据各位大佬的回复,还有一种方式:

Number("1234564").toLocaleString()
复制代码
关注下面的标签,发现更多相似文章
评论

查看更多 >