`
RednaxelaFX
  • 浏览: 3016048 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

消除一个IPv4地址中每段的前导0

阅读更多
刚才有个师兄问,说在JavaScript里要怎么用正则表达式来消除IPv4地址中每段的前导0。他原本是用parseInt+concat来做,觉得麻烦所以想试试正则表达式。输入的字符串保证是a.b.c.d这样的形式,其中a、b、c、d都是1-3位的数字。本来应该顺便验证一下是否满足0-255的范围的……算了。

想了一下,给了他这个:
/^0*(\d+\.)0*(\d+\.)0*(\d+\.)0*(\d+)$/

(编辑:呃,无视这个吧。我傻了,觉得应该要匹配IPv4里的那3个点('.')来保证输入的字符串的格式是正确的;不过在这个具体的场景其实前面的有别的程序已经保证了输入是正确的IPv4地址了,我这里没必要弄那么麻烦。
看帖子最后的那个……)


用法是例如:
function stripLeadingZeros(ip) {
  var re = /^0*(\d+\.)0*(\d+\.)0*(\d+\.)0*(\d+)$/
  return ip.replace(re, '$1$2$3$4')
}

///////////////////////////

ip = stripLeadingZeros('010.64.01.00') // 10.64.1.0

在IE7和Rhino 1.7R1里测了下都OK。

但是这个正则表达式貌似太麻烦了,师兄不肯用在他的production code里 T T
呜呜白写了。只好记在blog上了。
可恶的JavaScript标准,居然不支持/x选项,不然要是能给每节加上注释的话说不定就能通过 review了……

写得这么麻烦还是因为要处理整段都是0的状况。假如一段数字是0010,那用
/^(?:0*)(\d+)$/

就足以匹配和捕获了,捕获到的就是10。
但是如果整串数字都是0,要消除前导0就意味着要消除除了最后一个0之外的其它0。这就有点麻烦了。我能想到的最直观的办法就是照样用0*匹配前导0,然后在末尾看看是否到头了;如果到头了的话就强制让匹配位置后退一位。所以上面的正则表达式里每段匹配都加了一个顺序环视(?=\d)来做这个强制回退,变成0*(?=\d)。
这么一加整个表达式就变得好长……呜

[color=red](编辑:我的理解有误,这些正则表达式引擎都很聪明的会自动回溯,没有必要显式指定顺序环视的条件。
关键是在0*后面跟的是\d+,如果后面跟的是\d*,则必须要手工的让匹配位置回退,也就是必须加上(?=\d)的顺序环视条件;不过后者很明显是多余的了。
*与+有显著的区别。*是Kleene Star,在集合论中没有是基本操作之一;+是一个简写方式,展开来就是(假如要匹配的元素是a,那么)aa*。
如果一个正则表达式里有两个连续的*,而它们要匹配的表达式所对应的集合如果有交集,那么后一个*所能匹配到的交集内的内容就一定会被前一个*所匹配。例如(0*)(d*)用于匹配000时,总是$1为000而$2为空。
但如果是(0*)(\d+),先展开为(0*)(\d\d*),就可以看到这里不存在连续的两个*,中间被一个固定长度的\d分隔开了。正则表达式引擎就会在遇到这个\d时自动决定是否需要回溯。
关注一下这个例子:
/^.*(\d+)/

在匹配"Copyright 2008."的时候,$1只是8,而不是2008。展开来看,/^.*(\d\d*)/中固定长度的\d只迫使.*回退了1个字符,然后整个正则表达式就满足匹配了,因而不会继续回退。)[/color]

==========================================================================

不过回头再想了想,既然这里都没验证0-255的范围,干脆连IPv4的格式也不验证算了。那样正则表达式就可以短很多,这样:
function stripLeadingZeros(str) {
  return str.replace(/0*(\d+)/g, '$1')
}

///////////////////////////

ip = stripLeadingZeros('010.64.01.00') // 10.64.1.0

这个正则表达式:
/0*(\d+)/g

做的事情很简单,就是匹配输入字符串中任意连续的数字,并捕获前导0以外的部分到$1。

原理也很简单:
1、\d+可以匹配输入字符串中任意一串连续的数字;
2、任意一串连续的数字前面再加上一串0的话还是一串连续的数字;(涉及到+量词的贪婪性(匹配优先))
3、0*可以匹配任意一串连续的0或空串;
4、/0*(\d+)/匹配的是一串连续的0或空串,后面接上一串不为空并且不以0开头的数字;后面接的这串数字被捕获到第一个捕获分组中;
6、/g选项使得这个正则表达式匹配输入字符串中所有可能的匹配。

这里有把0*换成0+的诱惑,但改了之后:
/0+(\d+)/g

这个正则表达式就不能正确的将输入字符串中所有数字的前导0去除——不只是前导的0,它有可能匹配到位于数字中间的连续的0。
如果改成这样:
/0*(?=\d)/g

然后替换为空串,也无法满足要求。它会匹配到任意数字前的一个位置(零长度),也会匹配到任意一串连续的、后面还有数字的0。

前面的能正确消除前导0的正则表达式之所以正确,是因为那个表达式每次从整体来看总能匹配到最左最长的一串连续的数字,无论它是0开头(进入0*部分)还是其它数字开头(跳过0*直接进入\d+部分)。这样就能保证不会匹配到位于数字中间的连续的0。

同一个方法要是用Ruby写也一样:
def strip_leading_zeros(str)
  str.gsub /0*(?=\d)/, '\1'
end

分享到:
评论
2 楼 RednaxelaFX 2008-12-30  
cajon 写道

呵呵,路过...

啊,Colin老大 T T
最近都在忙些什么呢?Blog那边好像11月下半开始都没更新过了……Gendarme有没有试用过?
1 楼 cajon 2008-12-30  
呵呵,路过...

相关推荐

Global site tag (gtag.js) - Google Analytics