我在PHP中编写了一个非常简单的preg_match_all文件:
$fileName = 'A_DATED_FILE_091410.txt';
$matches = array();
preg_match_all('/[0-9][0-9]/',$fileName,$matches);
print_r($matches);
我的预期输出:
$matches = array(
[0] => array(
[0] => 09,
[1] => 91,
[2] => 14,
[3] => 41,
[4] => 10
)
)
我得到了什么:
$matches = array(
[0] => array(
[0] => 09,
[1] => 14,
[2] => 10
)
)
现在,在这个特殊用例中,这是更可取的,但我想知道为什么它与其他子串不匹配?还有,正则表达式可能会给我我预期的输出,如果是,它是什么?
答案 0 :(得分:7)
使用全局正则表达式(preg_match_all
使用的),一旦匹配,正则表达式引擎将继续从上一个匹配结束时搜索字符串。
在您的情况下,正则表达式引擎从字符串的开头开始,并前进到0
,因为这是匹配[0-9]
的第一个字符。然后它会前进到下一个位置(9
),由于这与第二个[0-9]
匹配,因此需要09
作为匹配。当引擎继续匹配时(因为它尚未到达字符串的末尾),它再次前进其位置(到1
)(然后重复上述步骤)。
另请参阅:First Look at How a Regex Engine Works Internally
如果您必须获取每2位数序列,您可以使用preg_match
并使用偏移来确定从哪里开始捕获:
$fileName = 'A_DATED_FILE_091410.txt';
$allSequences = array();
$matches = array();
$offset = 0;
while (preg_match('/[0-9][0-9]/', $fileName, $matches, PREG_OFFSET_CAPTURE, $offset))
{
list($match, $offset) = $matches[0];
$allSequences[] = $match;
$offset++; // since the match is 2 digits, we'll start the next match after the first
}
请注意,使用PREG_OFFSET_CAPTURE
标志返回的偏移量是匹配的 start 。
我有另一个解决方案,可以获得五场比赛,而不必使用偏移,但我只是为了好奇而在这里添加它,我可能不会在生产代码中自己使用它(它也是一个有点复杂的正则表达式)。您可以使用使用lookbehind的正则表达式在当前位置之前查找数字,并使用captures查看后备中的数字(通常情况下,结果是非捕获的):
(?<=([0-9]))[0-9]
让我们来看看这个正则表达式:
(?<= # open a positive lookbehind
( # open a capturing group
[0-9] # match 0-9
) # close the capturing group
) # close the lookbehind
[0-9] # match 0-9
因为lookarounds是零宽度并且不移动正则表达式位置,所以这个正则表达式将匹配5次:引擎将前进到9
(因为这是第一个满足lookbehind断言的位置)。由于9
匹配[0-9],引擎会将9
作为匹配项(但是因为我们在环视中捕获它,它还会捕获0
!)。然后引擎移动到1
。同样,lookbehind成功(并捕获),1
被添加为第一个子组匹配(依此类推,直到引擎命中字符串的结尾)。
当我们将此模式提供给preg_match_all
时,我们最终会得到一个看起来像的数组(使用PREG_SET_ORDER
标志将捕获组与完整匹配分组):
Array
(
[0] => Array
(
[0] => 9
[1] => 0
)
[1] => Array
(
[0] => 1
[1] => 9
)
[2] => Array
(
[0] => 4
[1] => 1
)
[3] => Array
(
[0] => 1
[1] => 4
)
[4] => Array
(
[0] => 0
[1] => 1
)
)
请注意,每个“匹配”的数字都不按顺序排列!这是因为lookbehind中的捕获组变为反向引用1,而整个匹配是反向引用0.我们可以按正确的顺序将它重新组合在一起,但是:
preg_match_all('/(?<=([0-9]))[0-9]/', $fileName, $matches, PREG_SET_ORDER);
$allSequences = array();
foreach ($matches as $match)
{
$allSequences[] = $match[1] . $match[0];
}
答案 1 :(得分:2)
搜索下一场比赛从上一场比赛后的第一个角色开始。因此,当09
中匹配091410
时,搜索下一场比赛将从1410
开始。
答案 2 :(得分:1)
此外,正如可能的正则表达式 给我预期的输出,如果是的话, 它是什么?
没有一个人会工作,因为它不会匹配同一部分两次。但你可以这样做:
$i = 0;
while (preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE, $i))
{
$i = $matches[0][1]; /* + 1 in many cases */
}
以上情况对一般情况不安全。根据模式的不同,你可能陷入无限循环。此外,您可能不希望[0][1]
,而是[1][1]
等等,这取决于模式。
对于这种特殊情况,我认为自己做这件事要简单得多:
$l = strlen($s);
$prev_digit = false;
for ($i = 0; $i < $l; ++$i)
{
if ($s[$i] >= '0' && $s[$i] <= '9')
{
if ($prev_digit) { /* found match */ }
$prev_digit = true;
}
else
$prev_digit = false;
}
答案 3 :(得分:1)
只是为了好玩,另一种方式:
<?php
$fileName = 'A_DATED_FILE_091410.txt';
$matches = array();
preg_match_all('/(?<=([0-9]))[0-9]/',$fileName,$matches);
$result = array();
foreach($matches[1] as $i => $behind)
{
$result[] = $behind . $matches[0][$i];
}
print_r($result);
?>