PHP如何避免从字符串中提取数字块时混合字母和数字?

3
我将编写一个PHP函数,从类似以下字符串中提取数字ID:

$test = '123_123_Foo'

起初我采用了两种不同的方法,其中一种使用preg_match_all()

$test2 = '123_1256_Foo';
preg_match_all('/[0-9]{1,}/', $test2, $matches);
print_r($matches[0]); // Result: 'Array ( [0] => 123 [1] => 1256 )'

和其他使用 preg_replace()explode() 的技术:

$test = preg_replace('/[^0-9_]/', '', $test);
$output = array_filter(explode('_', $test));
print_r($output); // Results: 'Array ( [0] => 123 [1] => 1256 )'

只要字符串不包含混合字母数字的字符,任何一种都可以很好地工作:

$test2 = '123_123_234_Foo2'

显而易见的结果是数组([0]=>123 [1]=>1256 [2]=>2) 所以我写了另一个正则表达式来消除混合字符串:
$test2 = preg_replace('/([a-zA-Z]{1,}[0-9]{1,}[a-zA-Z]{1,})|([0-9]{1,}[a-zA-Z]{1,}[0-9]{1,})|([a-zA-Z]{1,}[0-9]{1,})|([0-9]{1,}[a-zA-Z]{1,})|[^0-9_]/', '', $test2);
$output = array_filter(explode('_', $test2));
print_r($output); // Results: 'Array ( [0] => 123 [1] => 1256 )'

问题也很明显,更复杂的模式,如 Foo2foo12foo1 也能通过筛选器。这就是我遇到困难的地方。
回顾一下:
  • 从字符串中提取可变数量的数字块。
  • 字符串至少包含一个数字,并且可能包含由下划线分隔的其他数字和字母。
  • 仅提取未在字母前面或后面的数字。
  • 仅第一半字符串中的数字有意义。
由于只需要第一半部分,我决定使用 preg_split() 按字母或混合数字字母的第一个出现位置进行拆分:
$test2 = '123_123_234_1Foo2'
$output = preg_split('/([0-9]{1,}[a-zA-Z]{1,})|[^0-9_]/', $test, 2);
preg_match_all('/[0-9]{1,}/', $output[0], $matches);
print_r($matches[0]); // Results: 'Array ( [0] => 123 [1] => 123 [2] => 234 )'

我的问题的核心是,是否有更简单、更安全、更高效的方法来实现这个结果。


所以你想提取仅由下划线分隔的完全为数字的子字符串,并拒绝其他所有内容? - Darragh Enright
1
类似这样?https://eval.in/886873 - 我不会回答,因为我不确定我是否完全理解了你的问题措辞。 - Darragh Enright
$test2 = "123_123_234_1Foo2"; $ints = array_filter(explode('_', $test2 ), 'is_numeric'); var_dump($ints); - Emilio Gort
4个回答

2

使用 strtok

正则表达式并不是万能的,对于您要分割的字符串来说,有更简单的解决方案。以下任意一种方法都更加清晰易懂、易于维护,使用 strtok() 可能会更快:

  1. 使用 explode 创建并循环数组,逐个检查每个值。
  2. 使用 preg_split 进行相同操作,但具有更灵活的方法。
  3. 使用 strtok,因为它专门为此用例而设计。

针对您的情况的基本示例:

function strGetInts(string $str, str $delim) {
    $word = strtok($str, $delim);

    while (false !== $word) {
        if (is_integer($word) {
            yield (int) $word;
        }
        $word = strtok($delim);
    }   
}

$test2 = '123_1256_Foo';

foreach(strGetInts($test2, '_-') as $key {
    print_r($key);
}

注意: strtok的第二个参数是包含任何分隔符用于拆分字符串的字符串。因此,我的示例将把结果分组为由下划线或破折号分隔的字符串。

附加说明: 仅当字符串只需要在单个分隔符(仅下划线)上拆分时,使用explode方法可能会导致更好的性能。对于这种解决方案,请参见此线程中的其他答案:https://dev59.com/Qqbja4cB1Zd3GeqPdT5M#46937452


2
如果我理解您的问题正确,您想拆分一个下划线分隔的字符串,并过滤掉任何不是数字的子字符串。如果是这样,可以使用 explode()array_filter()ctype_digit() 实现;例如:
<?php

$str = '123_123_234_1Foo2';

$digits = array_filter(explode('_', $str), function ($substr) {
  return ctype_digit($substr);
});

print_r($digits);

这将产生:
Array
(
    [0] => 123
    [1] => 123
    [2] => 234
)

请注意ctype_digit()函数:

检查提供的字符串中所有字符是否都是数字。

因此,$digits仍然是一个字符串数组,尽管其中的元素是数字。
希望这有所帮助 :)

@darraghenright $digits = array_filter(explode('_', $str ), 'ctype_digit'); 你可以将该函数作为array_filter的第二个参数。 - Emilio Gort
@EmilioGort - 当然没问题!这是真的,而且由于它只有一行,使得代码非常简洁易读。我也知道这一点,但我更喜欢编写显式回调函数 - 我想那只是我的风格 :) 需要注意的是,回调函数要比将可调用对象作为字符串传递快很多。不过,我要承认的是,这种“参数”优化只是微观上的改善! ;) - Darragh Enright

2
在使用explode函数后,获取字符串中的纯数字部分。
$test2  = "123_123_234_1Foo2";
$digits = array_filter(explode('_', $test2 ), 'is_numeric');
var_dump($digits);

结果

array(3) { [0]=> string(3) "123" [1]=> string(3) "123" [2]=> string(3) "234" }

1
如果is_numeric()确定一个字符串是浮点数,它将返回true。在PHP 7.*之前,它还会对可以表示十六进制和二进制数字的字符串返回true。这可能对OP来说不是问题,但值得注意。 - Darragh Enright
{btsdaf} - Tony Chiboucas
is_numeric() 对于这个任务不是很可靠。看它如何崩溃:https://3v4l.org/Ro5gP - mickmackusa

0

使用单个preg_match_all()调用可以轻松完成此任务。

使用匹配一个或多个数字的模式,该模式:

  1. 在字符串开头或下划线之前,并且
  2. 在下划线或字符串结尾之后。

代码:(演示)

$test2 = '123_123_234_1Foo2';
preg_match_all('/(?<=^|_)\d+(?=_|$)/', $test2, $m);
var_export($m[0]);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接