哇,原来它是可能的(虽然很丑)!
如果您没有时间或懒得通读整个解释,这里是执行此操作的代码:
$str = '0 1 2 3 4 5 6 7 8 9 10 11 12 13 19 20 29 99 100 139';
$str = preg_replace("/\d+/", "$0~", $str);
$str = preg_replace("/$/", "#123456789~0", $str);
do
{
$str = preg_replace(
"/(?|0~(.*#.*(1))|1~(.*#.*(2))|2~(.*#.*(3))|3~(.*#.*(4))|4~(.*#.*(5))|5~(.*#.*(6))|6~(.*#.*(7))|7~(.*#.*(8))|8~(.*#.*(9))|9~(.*#.*(~0))|~(.*#.*(1)))/s",
"$2$1",
$str, -1, $count);
} while($count);
$str = preg_replace("/#123456789~0$/", "", $str);
echo $str;
现在让我们开始吧。
首先,正如其他人提到的那样,即使您循环它也不可能在单个替换中进行(因为您如何将相应的增量插入单个数字)。但是,如果您先准备字符串,则可以循环使用单个替换。这是我使用 PHP 的演示实现。
我使用了这个测试字符串:
$str = '0 1 2 3 4 5 6 7 8 9 10 11 12 13 19 20 29 99 100 139';
首先,让我们通过附加一个标记字符来标记我们想要增加的所有数字(我使用~,但您可能应该使用一些绝对不会出现在您的目标字符串中的疯狂的 Unicode 字符或 ASCII 字符序列。
$str = preg_replace("/\d+/", "$0~", $str);
由于我们将一次替换每个数字的一位数字(从右到左),因此我们只需在每个完整数字后添加该标记字符。
现在主要的技巧来了。我们在字符串的末尾添加了一个小“查找”(也用您的字符串中没有出现的唯一字符分隔;为简单起见,我使用了#)。
$str = preg_replace("/$/", "#123456789~0", $str);
我们将使用它来替换相应的后继数字。
现在是循环:
do
{
$str = preg_replace(
"/(?|0~(.*#.*(1))|1~(.*#.*(2))|2~(.*#.*(3))|3~(.*#.*(4))|4~(.*#.*(5))|5~(.*#.*(6))|6~(.*#.*(7))|7~(.*#.*(8))|8~(.*#.*(9))|9~(.*#.*(~0))|(?<!\d)~(.*#.*(1)))/s",
"$2$1",
$str, -1, $count);
} while($count);
好的,怎么回事?匹配模式对每个可能的数字都有一个替代方案。这会将数字映射到继任者。以第一种方案为例:
0~(.*#.*(1))
这将匹配任何0,后跟我们的增量标记~,然后匹配直到我们的作弊分隔符和相应的继任者(这就是我们将每个数字放在那里的原因)。如果您看一下替换,它将被$2$1 替换(然后将是1,然后我们在~ 之后匹配的所有内容将其放回原位)。请注意,我们在此过程中删除了~。从0 增加一个数字到1 就足够了。数字已成功增加,没有结转。
接下来的 8 个选项对于数字 1 到 8 完全相同。然后我们处理两种特殊情况。
9~(.*#.*(~0))
当我们替换9 时,我们不会删除增量标记,而是将其放在生成的0 的左侧。这(结合周围的循环)足以实现结转传播。现在还剩下一种特殊情况。对于仅由9s 组成的所有数字,我们将在数字前面加上~。这就是最后一个替代方案的用途:
(?<!\d)~(.*#.*(1))
如果我们遇到一个前面没有数字的~(因此是否定的lookbehind),它一定是一直通过一个数字,因此我们只需将它替换为1。我认为我们甚至不需要消极的lookbehind(因为这是检查的最后一个替代方案),但这样感觉更安全。
关于整个模式周围的(?|...) 的简短说明。这样可以确保我们始终在相同的引用 $1 和 $2 中找到两个匹配项(而不是字符串中的更大数字)。
最后,我们添加了 DOTALL 修饰符 (s),以使其适用于包含换行符的字符串(否则,只会增加最后一行中的数字)。
这是一个相当简单的替换字符串。我们只需先写$2(我们在其中捕获了后继标记,可能还有结转标记),然后我们将匹配到的其他所有内容用$1 放回原处。
就是这样!我们只需要从字符串末尾删除我们的 hack,我们就完成了:
$str = preg_replace("/#123456789~0$/", "", $str);
echo $str;
> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 20 21 30 100 101 140
所以我们可以完全在正则表达式中做到这一点。我们唯一的循环总是使用相同的正则表达式。我相信这是我们在不使用preg_replace_callback() 的情况下所能达到的最接近的结果。
当然,如果我们的字符串中有带小数点的数字,这会造成可怕的后果。但这可能可以通过第一次准备更换来解决。
更新:我刚刚意识到,这种方法会立即扩展到任意增量(不仅仅是+1)。只需更改第一个替换。您附加的~ 的数量等于您应用于所有数字的增量。所以
$str = preg_replace("/\d+/", "$0~~~", $str);
会将字符串中的每个整数递增3。