分享 3-15 个字符,可以用 [a-z0-9_\-] 字符,必须用数字或者字母开头,且不能是纯数字

mvj3 · January 26, 2013 · Last by douxiance replied at September 10, 2015 · 9181 hits

# encoding: UTF-8
# 老大提了个用单个正则表达式来匹配个性域名ID的需求,具体是:3-15个字符,可以用[a-z0-9_\-]字符,必须用数字或者字母开头,且不能是纯数字

# 让我们来分步化解
# 对于,3-15个字符,可以用[a-z0-9_\-]字符,稍微了解点正则表达式的同学马上就可以写出如下正则
/^[a-z0-9_\-]{3,15}$/i

# 让我们来再加一个条件,必须用数字或者字母开头
/^[0-9a-z][a-z0-9_\-]{2,14}$/i

# 最后一个条件是,且不能是纯数字。
# 最初我大概的想到是用 或(|) 来做,一个是数字开头,另一个是字母开头,剩余的是否纯数字在后面判断。但这剩余里还是会遇到判断在哪个位置里已经出现了字母的问题。所以这里就需要正则表达式里非匹配获取的功能了。

# 维基百科里给出的是 (?=pattern) ,它表示 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符「(|)」来组合一个模式的各个部分是很有用。例如「industr(?:y|ies)」就是一个比「industry|industries」更简略的表达式。具体见 http://zh.wikipedia.org/wiki/正则表达式

# stackoverflow里有更通俗的例子 http://stackoverflow.com/questions/1559751/regex-to-make-sure-that-the-string-contains-at-least-one-lower-case-char-upper
# ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*(_|[^\w])).+$
# A short explanation:
# ^                  // the start of the string
# (?=.*[a-z])        // use positive look ahead to see if at least one lower case letter exists
# (?=.*[A-Z])        // use positive look ahead to see if at least one upper case letter exists
# (?=.*\d)           // use positive look ahead to see if at least one digit exists
# (?=.*[_\W])        // use positive look ahead to see if at least one underscore or non-word character exists
# .+                 // gobble up the entire string
# $                  // the end of the string

# 这样我们就在 /^[0-9a-z][a-z0-9_-]{2,14}$/i 里再加上个 (?=.*[a-z_\-]) 就可以匹配不是纯数字了
@regexp = /^(?=.*[a-z_\-])[0-9a-z][a-z0-9_\-]{2,14}$/i

puts "合法测试"
%w[c11213311 mvj3 123mmmmm iceskysl 3_3].each do |str|
  puts "#{str}   =>   #{str.match(@regexp)}"
end

puts "\n"

puts "非法测试"
%w[123 12345 mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm _ssssss 你好].each do |str|
  puts "#{str}   =>   #{str.match(@regexp) || '没有匹配'}"
end


# 测试结果如下:
__END__
合法测试
c11213311   =>   c11213311
mvj3   =>   mvj3
123mmmmm   =>   123mmmmm
iceskysl   =>   iceskysl
3_3   =>   3_3

非法测试
123   =>   没有匹配
12345   =>   没有匹配
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm   =>   没有匹配
_ssssss   =>   没有匹配
你好   =>   没有匹配

原发表在 http://code.eoe.cn/83

我是来歪楼的,非要用一个正则表达式也没啥大问题,尽管很多时候写成多个更容易。

我只是在想为啥需求文档不用 Prolog 或者类似的语言来写。假如需求文档不能运行,你如何验证你的实现能满足需求...

试着用 Prolog 写了下,感觉其实大部分还是很好理解的。

rule_3_to_15_characters(Name) :-
    length(Name, L),
    L >= 3,
    L =< 15.

valid_character(C) :-
    char_type(C, alpha),
    char_type(C, lower).
valid_character(C) :-
    char_type(C, digit).
valid_character(C) :-
    member(C, "_-").

rule_valid_characters(Name) :-
    maplist(valid_character, Name).

rule_start_with_alnum([C|_]) :-
    char_type(C, alnum).

rule_have_non_digit_character(DomainName) :-
    member(C, DomainName),
    \+ char_type(C, digit).

valid_domain_name(DomainName) :-
    rule_3_to_15_characters(DomainName),
    rule_valid_characters(DomainName),
    rule_start_with_alnum(DomainName),
    rule_have_non_digit_character(DomainName),
    !.

感谢分享。

这个正向零宽断言用的真是很妙啊,nice

#1 楼 @bhuztez 。。。Prolog 这个似乎太长了,超过了(自然语言描述)需求的语义长度,语言本身和需求杂糅得太多,如分开的 valid_character 和最后合并的 valid_domain_name 函数。程序终究和自然语言不一样,它讲求数理上的递归,否则英语国家(一般意义上)的人都会编程了。

来个易懂的注释版

DomainRegex = Regexp.new([
 "^",
 "(?=.*[a-z_\-])", # 不能全是数字
 "[0-9a-z]", # 必须用数字或者字母开头
 "[a-z0-9_\-]{2,14}", # 3-15个字符,可以用[a-z0-9_\-]字符
 "$"
].join, 'i')

最完美的信息表示,它的熵应该是最低的。如果正则表达式支持变量可以更进一步减少重复信息:)

#4 楼 @mvj3

最完美的信息表示,它的熵应该是最低的

原来你不懂熵啊,好好一句话被你说反了。熵值和信息量可以认为表示的是同一个东西。表达相同的信息,长度越短,那么冗余就越少。

#4 楼 @mvj3

我不是反对代码短,而是为了歪楼,讨论一下有没有可能通过运行需求文档来验证实现...

超过了(自然语言描述)需求的语义长度

不是的,自然语言充满了大量背景知识的假设,所以用 Prolog 会长过自然语言描述是很正常的。而且很多时候,长一点并不是大问题,歧义才是问题。

Perl 风格的正则表达式可以认为是一种特殊的 Prolog DSL。在处理简单的字符串匹配上比 Prolog 好也是可以预期的。

很多时候,感觉用类似 Prolog 的语言来表示需求还是蛮合适的,而且学习成本也不高。我没有说最终代码一定得用 Prolog 来写。而且之前看过一个 slide,也的确是有人这么做了。

http://www.erlang-factory.com/conference/SFBay2012/speakers/RichardCarlsson

#5 楼 @bhuztez 多谢指出,我是不太懂物理。。。最完美的信息(熵)表示,它的冗余应该是最低的。

#7 楼 @mvj3 前几天刚学会熵现学现卖了...

#4 楼 @mvj3 正则有个 x 参数,可以无视空格添加注释的

/\A
  (?=.*[a-z_\-])    # 不能全是数字
  [0-9a-z]          # 必须用数字或者字母开头
  [a-z0-9_\-]{2,14} # 3-15个字符,可以用[a-z0-9_\-]字符
\z/xi

另外注意 ^$ 是行首和行末,匹配字符串头尾应该用 \A\z, 测试用例:"\nabc"

#6 楼 @bhuztez 需求,往往只是一个眼神,写成文档太不解风情了...

#6 楼 @bhuztez 不管是正则表达式 还是 把匹配模式扩展成更日常语言化的 Prolog(我不太了解逻辑的 Prolog 还是函数式的其他编程语言),两者根本还是在图灵机上的,执行行为都是一致的。而停机问题就需要更高级的人脑去判断了。

语言的该短该长还是依赖受众群体,更甚至场合去选择的。举个相对论的例子,:E=MC^2 是最能表达其核心思想的,涉及运算时套用公式更简单,如果是向别人解释,那就有论文版,浅论版,和姑娘同坐版等各种了。所以是否采用需求文档或者其他测试工具,就各取所需好了。

#9 楼 @luikore 哈哈,这个多行的用法是学正则表达式那时还记得的。(?=pattern) 模式是查了才知道的。

运行了一下还真是 "\n123m".match DomainRegex => #

知识没有穷尽啊(我只要一个真理),亚历山大。。。回答的时候又查了 N 多资料

另外... 没几种写法还能称作回字吗?

/\A
  (?= .*\D)
  (?! [_-])
  [[:alnum:]_-]{3,15}   # alnum = alpha + number
\z/x

正则里的断言就和 prolog 的 rule 差不多啦 各种预定义好的 unicode character class 就和 prolog 里的类型差不多?

#13 楼 @luikore 。。。我只能说我只是来追求真理的

太牛了,我的正则刚入门。

编程就是将自然语言需求写成形式语言的过程... 程序员之所以存在的一个理由就是客户说不清楚...

楼主用肯定断言表达 "不能全是数字" 是经过了一次逻辑运算的结果: ¬(∀c, c 是数字) => ∃c, ¬(c 是数字)

按照要求直接用否定断言 (?!) 也可以:

/\A
  (?! \d+\z)
  (?! [_-])
  [[:alnum:]_-]{3,15}
\z/x

#5 楼 @bhuztez 仔细查了“熵”,可能是我既看物理又看计算机科学有点搞乱了,思考无序冗余简洁去了。”熵“可分为”信息熵“,”热力学熵“,"生态学熵”等,它们的联系据我现在所知只是公式原理相同而已。

你所指的“熵”是信息论里的熵。维基百科 信息熵 说 "在信息论里面,熵是对不确定性的测量。但是在信息世界,熵越高,则能传输越多的信息,熵越低,则意味着传输的信息越少。" ,比如"China"和"中国“的熵是一样的,也可以说所含信息是一样的如果我们只是常规的指代,如果硬要从别的角度出发,也就是你说的语言的歧义,当你说语音而没有指名大小写的时候,"china"在英语里是瓷器的意思,而"中国"可能又导致领土政治问题。所以从String#size这个角度来说,确实应该是最低最小的。在String这个角度,正则确实降低了信息(熵)。我上面提到的熵,说的是正则表达式更有序简洁(当然对不了解正则的人来说增加了学习成本)。 上段算解答了。如果用上压缩算法,这个(信息)熵还可以更小,但是得不算上压缩和减压需要的能量和算法和字典库,否则就越来越扯了(如何鉴定孤立系统的范围)。

热力学里的熵指的是一个封闭系统的无序度,但是无论如何无序,按热力学第二定律,除了熵增加,能量和质量是不会有所增加或减少的。

另外我平时也在写我的学习记录,算是生活乐趣。

#16 楼 @luikore 客户没说完全或没想清楚是另外的事。程序员干的事就是用图灵机来帮客户干图灵机能干的事。為什麼不是客户自己做,而是描述自然语言的需求让别人(程序员)做,是因为别人做的更经济或更专业(比自己花时间和精力等)。為什麼程序员描述形式语言的需求让机器做,因为机器做逻辑的东西最快。

我只能说我写的比普通的程序员好一点,但是没有你写的那么专业,这里只是层次和类型之分。刚才相对论的例子也讲了。

最后一句,有注释和测试的程序才是完解~

#13 楼 @luikore Prolog 里也是用一下 char_type :-p

从 backtracking 的角度看,Perl 风格的正则表达式就是 Prolog 的某种 DSL。

看过大神们的研讨,觉得自己早该补补这块缺了

You need to Sign in before reply, if you don't have an account, please Sign up first.