如何用四行代码实现数字的千分位分割?
前言
最近遇到一道手写面试题,感觉还比较有趣,分享给大家。
要求实现一个千分位逗号分割的方法。 小数位也需要分割。
例如:12345678.1415926 ==》 '12,345,678.141,592,6'
最终实现版本
借助正则,精简到四行代码。
//千位分隔符
function formatNumber(num) {
var source = String(num).split("."); //按小数点分成2部分
source[0] = source[0].replace(new RegExp("(\\d)(?=(\\d{3})+$)", "ig"), "$1,"); //只将整数部分进行都好分割
source[1] = source[1].replace(new RegExp("(\\d{3})", "ig"), "$1,");
return source.join("."); //再将小数部分合并进来
}
formatNumber(12345678.1415926);
相关正则的讲解
\d
表示数字。 $
符号代表结尾。
?=
是非捕获元, 表示匹配前面的。 例如: exp1(?=exp2):查找 exp2 前面的 exp1。
{3}
表示 3 次,\d{3}
表示 3 个相连的数字。
i
: 表示不区分大小写,这里也可以不用。
g
: 表示全部匹配。
$1
: 表示引用第一个元组。小括号包裹的算作一个组。
+
: 表示匹配前面的表达式一次或多次。
为什么这里使用双反斜杠?
首先字符串中的\被编译器解释为\ ——-》 第一步,编译器将字符串转变为“正则表达式”\”, 然后作为正则表达式,\.又被正则表达式引擎解释为. —————-> 第二步,才开始把第一步的结果当做是正则表达式,开始进行匹配!
如果在字符串里只写\.的话,第一步就被直接解释为.,之后作为正则表达式被解释时就变成匹配任意字符了
先解释一下(\\d)(?=(\\d{3})+$)
首先看?=
后面是是 3 个数字为一组,匹配一次或多次,就是从后面数,找 3 的倍数个数字。 其实?=
表示匹配前面的,也就是匹配到的是 ‘12345’, ‘12’, 分别替换为$1,
,效果就是在其后添加逗号。
小数部分
有了上面的基础,小数后面的匹配就更简单了,从前面数,每三个数字后面添加逗号就好了。
直接写成(\\d{3})
即可。
其他实现方式?
这里我想说一下我第一版实现的方式
主要思路是:
- 小数点分割成整数部分和小数部分;
- 整数部分倒置之后,可和小数部分一样处理;
- 从前往后,三位分割成一个数组
- 分组之后用逗号 join
- 整数部分倒置恢复
- 拼接整数部分与小数部分
- 完成
//千位分隔符
function formatNumber(num) {
var source = String(num).split("."); //按小数点分成2部分
return reverseStr(sepNum(reverseStr(source[0]))) + "." + sepNum(source[1]);
}
// 字符串翻转
function reverseStr(str) {
return str.split("").reverse().join("");
}
function sepNum(num) {
// 这里之所以切成数组是因为方便后面执行Splice方法
!Array.isArray(num) && (num = num.split(""));
// 准备一个数组用于盛放切割之后的数组
const res = [];
while (num.length > 3) {
// 每三位切割
res.push(num.splice(0, 3).join(""));
}
// 最后不大于三位的数字直接推到数组中
res.push(num.join(""));
// 字符串组装
return res.join(",");
}
formatNumber(12345678.1415926);
执行结果虽然没啥问题,但是两次反转字符显得不够聪明。
也许选择其他的字符串翻转方法,性能会不会好一点。
// 字符串翻转二
function reverseStr(str) {
let res = "";
for (let i = str.length; i; i--) {
res += str[i - 1];
}
return res;
}