我有一个规范,不允许用户创建包含一定数量的递增字母或数字的密码,例如:
abc
、xyz
、hij
、678
、789
或RST
。
我发现正则表达式匹配模式将匹配
aaa
或 777
,但不匹配升序序列。
如何匹配序列。
你可以使用这样的东西:
function inSequence($string, $num = 4) {
$i = 0;
$j = strlen($string);
$str = implode('', array_map(function($m) use (&$i, &$j) {
return chr((ord($m[0]) + $j--) % 256) . chr((ord($m[0]) + $i++) % 256);
}, str_split($string, 1)));
return preg_match('#(.)(.\1){' . ($num - 1) . '}#', $str);
}
一种方法是逐步遍历字符串的每个字符并跟踪连续递增的字母数字字符的数量。从单个循环中尽早返回将确保获得最佳性能。
代码:(PHP8.3及以上版本的演示)(如果您使用PHP5.4到PHP8.2则演示)
function hasTooManyAscChars($str, $limit = 3) {
if ($limit === 1) {
throw new Exception('Using 1 as $limit parameter will cause failure on any alphanumermic character.');
}
$i = 1;
$prev = null;
foreach (mb_str_split($str) as $char) {
if (ctype_alnum((string) $prev) && $char === str_increment($prev)) {
++$i;
if ($i === $limit) {
return true;
}
} else {
$i = 1;
}
$prev = $char;
}
return false;
}
另一种方法(技术上可以写成单行)是使用正则表达式仅隔离由数字、小写或大写字符组成的子字符串,其长度等于声明的限制。如果没有合格的子字符串,则无需执行任何其他操作 - 密码已通过此验证规则。需要明确的是,前瞻允许隔离字符 s 1 到 4,然后是 2 到 5,等等(如果它们符合条件)。
如果存在匹配项,则迭代它们以确定它们是否按升序排序并且没有间隙。在
array_reduce()
中,我使用按位 OR 运算符 (|
) 来确保一旦遇到 true
求值,就不再进行处理函数调用——这是性能的最佳优化,同时更早返回是*不可能的(不可能——我的意思是我必须以一种相当不优雅的方式抛出异常来破坏array_reduce()
内的迭代)。
代码:(演示)(低至PHP5.5的过时版本演示)
function hasTooManyAscChars($str, $limit = 3) {
if ($limit === 1) {
throw new Exception('Using 1 as $limit parameter will cause failure on any alphanumermic character.');
}
return preg_match_all(
"/(?=(?|(\d{{$limit}})|([a-z]{{$limit}})|([A-Z]{{$limit}})))/u", #capture N digits, N lowers, or N uppers
$test,
$m,
PREG_SET_ORDER
)
&& #only continue processing if matches are made
array_reduce(
array_column($m, 1),
fn($result, $chars) => $result
| #if $result is true, don't process rightside
(
count_chars($chars, 3) === $chars #mode 3 will return unique chars in asc order
&&
count(range($chars[0], $chars[$limit - 1])) === $limit #number of elements in range equals limit
),
false
);
}
测试输入变量和函数执行脚本:
$tests = [
'Asdfghjk',
'135792456790',
'cremnophobia',
'8675309',
'analyzable',
'UNDERSTUDY',
'EasyAsABC1234',
'Çüéâäàå',
"\u{1f600}\u{1f601}\u{1f602}\u{1f603}\u{1f604}",
];
$limit = 4;
foreach ($tests as $test) {
printf(
"%15s: %s\n",
$test,
hasTooManyAscChars($test, $limit) ? 'Fail' : 'Pass'
);
}
使用相同测试程序的任一方法的输出:
Asdfghjk: Pass
135792456790: Fail
cremnophobia: Fail
8675309: Pass
analyzable: Pass
UNDERSTUDY: Fail
EasyAsABC1234: Fail
Çüéâäàå: Pass
😀😁😂😃😄: Pass