正则校验数字字符串是否符合指定字符集数量限制问题排查
问题分析与解决方案
你的正则之所以所有测试都返回true,核心有两个问题:
- 未锚定整个字符串:
preg_match默认只要字符串中存在任意一段匹配正则的子串就返回true,比如08228282里随便一个0或2都能匹配你的正则,自然会返回true,完全没校验整个字符串的合法性。 - 结构逻辑错误:你写的
0{0,2}1{0,4}2{0,3}...要求字符严格按0→1→2→…→9的顺序出现,但你的输入数字是任意乱序的,比如90204394这种字符串根本不可能匹配这个顺序的正则——但之前返回true还是因为子串匹配的问题,掩盖了这个逻辑错误。
方案1:统计字符出现次数(推荐,简单可靠)
直接统计输入字符串中每个数字的出现次数,和给定的字符集上限对比,逻辑清晰且易维护:
function canFormNumber($string) { // 定义字符集的最大允许数量 $charLimits = [ '0' => 2, '1' => 4, '2' => 3, '3' => 2, '4' => 3, '5' => 5, '6' => 2, '7' => 5, '8' => 2, '9' => 2 ]; // 统计每个数字的出现次数 $counts = count_chars($string, 1); foreach ($counts as $charAscii => $count) { $digit = chr($charAscii); // 检查当前数字是否超过限制 if (!isset($charLimits[$digit]) || $count > $charLimits[$digit]) { return false; } } // 额外校验长度在4-8位之间 $length = strlen($string); return $length >= 4 && $length <= 8; }
方案2:正则实现(如果必须用正则)
需要用正向预查来独立限制每个数字的出现次数,同时用^和$锚定整个字符串,确保完全匹配:
$pattern = '/^ (?=(?:[^0]*0){0,2}[^0]*$) # 0最多出现2次 (?=(?:[^1]*1){0,4}[^1]*$) # 1最多出现4次 (?=(?:[^2]*2){0,3}[^2]*$) # 2最多出现3次 (?=(?:[^3]*3){0,2}[^3]*$) # 3最多出现2次 (?=(?:[^4]*4){0,3}[^4]*$) # 4最多出现3次 (?=(?:[^5]*5){0,5}[^5]*$) # 5最多出现5次 (?=(?:[^6]*6){0,2}[^6]*$) # 6最多出现2次 (?=(?:[^7]*7){0,5}[^7]*$) # 7最多出现5次 (?=(?:[^8]*8){0,2}[^8]*$) # 8最多出现2次 (?=(?:[^9]*9){0,2}[^9]*$) # 9最多出现2次 [0-9]{4,8}$ # 限制长度为4-8位的数字 /x'; function canFormWithRegex($string) { global $pattern; return preg_match($pattern, $string) === 1; }
每个正向预查会忽略字符顺序,单独检查对应数字的出现次数是否符合要求,最后确保整个字符串是4-8位的数字。
内容的提问来源于stack exchange,提问作者Scott Chagall
相关产品推荐
相关产品推荐

