前端练习49 判断美元符号格式

使用正则表达式判断美元符号格式的练习题,以及使用正则表达式为数字添加千分符

知识点

正则表达式

题目

完成一个函数isUSDFormat返回true/false来判断一个字符串是否符合美元格式:

  1. $开头
  2. 如果是小数,保留两位小数;如果不是小数则不显示小数部分
  3. 整数部分从小数点上一位开始每隔三位用,分割开来
  4. 如果整数部分从数字0开始,则只会显示一位0

例如:

1
2
3
4
5
6
7
isUSDFormat('$1') // => true
isUSDFormat('$1.0') // => false
isUSDFormat('$100,000.00') // => true
isUSDFormat('$0,000.00') // => false
isUSDFormat('$0.00') // => true
isUSDFormat('$11,23,345.33') // => false
isUSDFormat('$1,123,345.33') // => true

实现

这道题目和之前在面试中遇到过的为数字添加逗号的问题有一点点类似,先来看这道题目。

正则表达式是非常强大的,如果不用正则表达式,需要在函数里面进行拆分判断,所以还是需要多练习正则的使用

这道题,我又没有做出来,还是水平太低,看了讨论区的答案,好好分解一下:

实现,要判断的格式分为三个部分,就是$加上整数部分再加上(肯能存在的)小数部分:

1
/^$(整数部分)(小数部分)?$/

先看比较容易的小数部分,小数部分如果存在时,构成就是小数点加上两位数字,所以:

1
2
// 小数部分
/\.\d{2}/

再来看整数部分,整数部分大体上可分为两种情况,情况1是以0开始,情况2就是不以0开始,那么两种情况可以用|来分割,两种情况都是满足要求的:

1
2
// 整数部分
((情况2)|0)

再来看情况2,它有两个要求,首位不能是0,并且从末尾(单词结束或者小数点位置)起每三位用,分割,所以情况2为:

1
/[1-9]\d{0,2}(,\d{3})*/ 

上面表达的意思就是第一位是非0的整数,然后接着的数字可能是0,1,2三种,也就是说三面这一半匹配了x/xx/xxx三种形式的数字,后面如果再有数字的话就是后面括号中的内容,必须接着一个逗号和三位数字,当然这部分也可以没有,所以用*来限制数量

所以拼接到一起就是:

1
const isUSDFormat = str => /^\$([1-9]\d{0,2}(,\d{3})*|0)(\.\d{2})?$/.test(str)

为数字添加逗号

说说拿到面试题,很简单,就是使用正则为一个数字,从右向左每三位添加一个,

这道题目我当时没有用直接用正则做出来,因为我觉得正则是从左至右进行判断,而现在是从右至左添加,所以不知道怎么搞。

我当时的做法就是将字符串反转后进行添加,添加完了再反转回去。

如果不采用这种形式怎么实现呢?

首先要了解一下在正则中的先行断言

先行断言是是用来判断位置的,比如x(?=y),代表的是如果x后面跟的是y,那么就匹配x,在xyz中,匹配结果就是x,不包括y,因为y是作为先行断言进行判断位置的。

与先行断言对应的就是后行断言(?!),比如x(?!y)表示后面不是y才会匹配x,所以xyz中的x就不会被匹配,而xzy中的x会被匹配

然后再来看一下我们要用到的\b\B,它们是用来匹配位置的(注意是匹配,而不是判断),\b匹配的是单词边界,而\B正好相反,也就是匹配非单词边界的位置

我的理解是,比如123\b匹配是1之前和3之后的位置,\B匹配的是12以及23之间的位置

1
2
3
4
   1   2   3   
| | | |
| | | |
\b \B \B \b

那么如果用replace方法对\b\B的匹配进行替换是什么效果呢?

1
2
3
let a = '123'.replace(/\b/g, '!')

a // "!123!"

实现了添加的效果,因为被我们替换掉的是一个位置,而非一个字符。这就是我们能够使用replace实现需求的原因。

然后来看具体的正则表达式,以12345举例

我们首先要明确,要匹配的就是一个位置,这个位置将被替换为,,这个位置的特殊性是它后面应该跟着3个数字

到目前位置我们的正则表达式就是:

1
/\B(?=(\d{3}))/

上面,\B匹配非单词边界,匹配的结果将是12/23/34/45各自数字之间的位置

然后后面通过一个先行断言对\B进行了约束,那就是这个位置后面必须跟着3个数字,那现在匹配的结果就是12/23之间的位置了,都可以满足要求

实际上12之间后面跟着3个数字,3个数字后面又跟着一个数字,而我们要的不是这样,它后面需要是单词的结束(或者说是单词的边界),所以需要在后面增加另外的位置判定($或者\b

1
/\B(?=(\d{3})\b)/

这样就能正确12345,而对于12345123456,可能有多个符合条件的先行断言位置要匹配,所以需要增加量词+

1
/\B(?=(\d{3})+\b)/

我之前理解错了,以为是有多个\B要匹配,+应该加在最外层的括号外边,其实不是,多个\B要匹配是replaceg完成的

所以最后的结果是:

1
const reformat = str => str.replace(/\B(?=(\d{3})+\b)/g, ',')

这只是对整数部分的处理,如果有小数部分,我没想到怎么直接用正则一次搞定,还是需要先判断是否有小数,然后将整数部分分割出出来单独处理,然后再把小数部分拼接上。

参考