哇,事实证明这是可能的(尽管很丑)!
如果您没有时间或懒得通读整个解释,这里是执行它的代码:
$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
从to增加一个数字1
就足够了。数字已成功增加,没有结转。
1
接下来的 8 个选项与的数字完全相同8
。然后我们处理两个特殊情况。
9~(.*#.*(~0))
当我们替换 时9
,我们不会删除增量标记,而是将其放在结果的左侧0
。这(结合周围的循环)足以实现结转传播。现在还剩下一种特殊情况。对于仅由9
s 组成的所有数字,我们将~
在数字前面加上。这就是最后一个替代方案的用途:
(?<!\d)~(.*#.*(1))
如果我们遇到 a~
前面没有数字(因此是否定的向后看),它一定是一直通过一个数字的,因此我们只需将它替换为 a 1
。我认为我们甚至不需要消极的后视(因为这是检查的最后一个替代方案),但这样感觉更安全。
(?|...)
关于整个图案的简短说明。这确保我们总是在相同的引用中找到两个匹配项的替代项$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
.