「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」
前言
最近遇到一道手写面试题,感觉还比较有趣,分享给大家。
要求实现一个千分位逗号分割的方法。 小数位也需要分割。
例如: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;
}
这样的话时间复杂度只有O(n)。
总结
虽然使用正则匹配来解这道题代码简洁度非常高,只需要仅仅四行代码,但是这需要我们对正则的语法和匹配符号非常所熟悉才行。可能有的时候想到用正则能方便解决,笔试的时候如果突然想不到具体的匹配符号,也是非常令人捉急的。所以第二种解题思路也可以作为参考。
还有更好的解决方案吗?欢迎大佬在评论区交流讨论。
如果本内容对你有帮助,也请您动动手指点个⭐️哦~,这将会是我继续努力的创作的动力源泉!
补充优化
距离上次做这道题已经一个月过去了,今天再次复习这道题目,确实已经忘记了正则的方法,对?=这个非捕获元的理解不够,所以很难记住。
在方法二的基础上进行了改进,
// 千分位分隔
function seprate(str) {
const list = str.toString().split(".");
const res = [[], []];
for (let i = list[0].length; i > 0; i = i - 3) {
const element = list[0].substring(i - 3, i);
console.log(element);
res[0].unshift(element);
}
for (let j = 0; j < list[1].length; j = j + 3) {
const element = list[1].substring(j, j + 3);
res[1].push(element);
}
return res.map(e => e.join(",")).join(".");
}
console.log(seprate(12345678.1415926));
这次思路更加清晰易懂,避免了字符串翻转等冗余逻辑。
- 小数点切割成数组的两项
- 正数部分从后往前遍历,每隔三位切割成数组的一项
- 小数部分从前往后遍历切割,三位切割成数组的一项
- join res的两个数组项并合并正负两个数组即可