是否可以使用正则表达式替换来增加数字?当然,不使用评估/基于功能的替换.
这个问题的灵感来自另一个问题,其中提问者希望在文本编辑器中增加数字.可能有更多的文本编辑器支持正则表达式替换,而不是支持全脚本编写的文本编辑器,因此如果存在正则表达式,则可以方便地使用正则表达式.
而且,我经常从聪明的解决方案中学到整齐的东西,几乎无用的问题,所以我很好奇.
假设我们只讨论非负十进制整数,即\d+
.
是否有可能在一次替换?或者,有限数量的替换?
如果没有,是否至少可以给出一个上限,例如数字高达9999?
当然,它是可行的,给定一个while循环(在匹配时替换),但我们在这里寻求无循环解决方案.
Mar*_*der 42
哇,事实证明这是可能的(尽管很难看)!
如果您没有时间或无法阅读整个解释,这里是执行此操作的代码:
$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;
Run Code Online (Sandbox Code Playgroud)
现在让我们开始吧.
首先,正如其他人提到的那样,即使你循环它也不可能在一次替换中(因为你如何将相应的增量插入到一个数字中).但是如果你先准备好字符串,那么就有一个可以循环的替换.这是我使用PHP的演示实现.
我用过这个测试字符串:
$str = '0 1 2 3 4 5 6 7 8 9 10 11 12 13 19 20 29 99 100 139';
Run Code Online (Sandbox Code Playgroud)
首先,让我们通过附加一个标记字符来标记我们想要增加的所有数字(我使用~
,但你应该使用一些疯狂的Unicode字符或ASCII字符序列,绝对不会出现在目标字符串中.
$str = preg_replace("/\d+/", "$0~", $str);
Run Code Online (Sandbox Code Playgroud)
由于我们将每次更换一个数字(从右到左),我们将在每个完整数字后添加该标记字符.
现在主要是黑客攻击.我们在字符串的末尾添加了一些"查找"(也用字符串中没有出现的唯一字符分隔;为了简单起见,我使用了它#
).
$str = preg_replace("/$/", "#123456789~0", $str);
Run Code Online (Sandbox Code Playgroud)
我们将使用它来替换相应的后继数字.
现在循环:
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);
Run Code Online (Sandbox Code Playgroud)
好的,发生了什么?匹配模式对于每个可能的数字都有一个替代方案.这会将数字映射到后继者.以第一个替代方案为例:
0~(.*#.*(1))
Run Code Online (Sandbox Code Playgroud)
这将匹配任何0
后跟我们的增量标记~
,然后它匹配我们的作弊分隔符和相应的后继者的所有内容(这就是我们将每个数字放在那里的原因).如果您浏览一下替换件,它将被替换为$2$1
(然后将其替换为1
我们在~
将其重新放回原位后匹配的所有内容).请注意,我们放弃了~
这个过程.从递增的数字0
来1
就足够了.数字成功递增,没有结转.
接下来的8个备选方案是完全的数字相同1
,以8
.然后我们处理两个特殊情况.
9~(.*#.*(~0))
Run Code Online (Sandbox Code Playgroud)
当我们替换时9
,我们不会删除增量标记,而是将其放在结果的左侧0
.这(与周围环路相结合)足以实现结转传播.现在剩下一个特例.对于仅由9
s 组成的所有数字,我们最终将~
在数字前面.这是最后一个替代方案:
(?<!\d)~(.*#.*(1))
Run Code Online (Sandbox Code Playgroud)
如果我们遇到一个~
没有前面的数字(因此是负面的后观),它必须一直通过一个数字,因此我们只需用一个数字替换它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
Run Code Online (Sandbox Code Playgroud)
所以我们可以完全用正则表达式来完成.唯一的循环我们总是使用相同的正则表达式.我相信这是我们可以在不使用的情况下获得的preg_replace_callback()
.
当然,如果我们的字符串中包含带小数点的数字,这将会产生可怕的事情.但是,第一次准备更换可能会照顾到这一点.
更新:我刚刚意识到,这种方法会立即扩展到任意增量(不仅仅是+1
).只需更换第一个替换品.~
您追加的数量等于您应用于所有数字的增量.所以
$str = preg_replace("/\d+/", "$0~~~", $str);
Run Code Online (Sandbox Code Playgroud)
会增加字符串中的每个整数3
.
use*_*991 37
对于我之前做过的一个特定实现,这个问题的主题让我很开心.我的解决方案恰好是两个替换,所以我会发布它.
我的实现环境是solaris,完整示例:
echo "0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909" |
perl -pe 's/\b([0-9]+)\b/0$1~01234567890/g' |
perl -pe 's/\b0(?!9*~)|([0-9])(?=9*~[0-9]*?\1([0-9]))|~[0-9]*/$2/g'
1 2 3 4 8 9 10 11 20 100 110 200 910 1000 1100 1910
Run Code Online (Sandbox Code Playgroud)
把它拉开以便解释:
s/\b([0-9]+)\b/0$1~01234567890/g
Run Code Online (Sandbox Code Playgroud)
对于每个数字(#),将其替换为0#~01234567890.第一个0是在需要舍入9到10的情况下.01234567890块用于递增."9 10"的示例文本是:
09~01234567890 010~01234567890
Run Code Online (Sandbox Code Playgroud)
可以单独描述下一个正则表达式的各个部分,它们通过管道连接以减少替换计数:
s/\b0(?!9*~)/$2/g
Run Code Online (Sandbox Code Playgroud)
在所有不需要舍入的数字前面选择"0"数字并将其丢弃.
s/([0-9])(?=9*~[0-9]*?\1([0-9]))/$2/g
Run Code Online (Sandbox Code Playgroud)
(?=)是正向前瞻,\ 1是匹配组#1.所以这意味着匹配所有后跟9s的数字,直到'〜'标记然后转到查找表并找到该数字后面的数字.替换为查找表中的下一个数字.因此,当正则表达式引擎解析数字时,"09~"变为"19~",然后变为"10~".
s/~[0-9]*/$2/g
Run Code Online (Sandbox Code Playgroud)
此正则表达式删除〜查找表.
And*_*ong 12
我设法让它在3个替换(无循环)中工作.
TL;博士
s/$/ ~0123456789/
s/(?=\d)(?:([0-8])(?=.*\1(\d)\d*$)|(?=.*(1)))(?:(9+)(?=.*(~))|)(?!\d)/$2$3$4$5/g
s/9(?=9*~)(?=.*(0))|~| ~0123456789$/$1/g
Run Code Online (Sandbox Code Playgroud)
说明
让我们~
成为一个特殊的角色,不要出现在文本的任何地方.
如果在文本中找不到某个角色,那么就没有办法让它看起来神奇.所以首先我们在最后插入我们关心的字符.
s/$/ ~0123456789/
Run Code Online (Sandbox Code Playgroud)
例如(点击这里重新提示),
0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909
Run Code Online (Sandbox Code Playgroud)
变为:
0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909 ~0123456789
Run Code Online (Sandbox Code Playgroud)接下来,对于每个数字,我们(1)递增最后一个非9
(或者前置一个,1
如果全部是9
s),以及(2)"标记"每个尾随组的9
s.
s/(?=\d)(?:([0-8])(?=.*\1(\d)\d*$)|(?=.*(1)))(?:(9+)(?=.*(~))|)(?!\d)/$2$3$4$5/g
Run Code Online (Sandbox Code Playgroud)
例如,(点击这里重新提示),我们的例子变成:
1 2 3 4 8 9 19~ 11 29~ 199~ 119~ 299~ 919~ 1999~ 1199~ 1919~ ~0123456789
Run Code Online (Sandbox Code Playgroud)最后,我们(1)用9
s 替换每个"标记"的s 组0
,(2)删除~
s,以及(3)删除末尾的字符集.
s/9(?=9*~)(?=.*(0))|~| ~0123456789$/$1/g
Run Code Online (Sandbox Code Playgroud)
例如,(点击这里重新提示),我们的例子变成:
1 2 3 4 8 9 10 11 20 100 110 200 910 1000 1100 1910
Run Code Online (Sandbox Code Playgroud)PHP示例
您可以将其复制并粘贴到http://www.writecodeonline.com/php中:
$str = '0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909';
echo $str . '<br/>';
$str = preg_replace('/$/', ' ~0123456789', $str);
echo $str . '<br/>';
$str = preg_replace('/(?=\d)(?:([0-8])(?=.*\1(\d)\d*$)|(?=.*(1)))(?:(9+)(?=.*(~))|)(?!\d)/', '$2$3$4$5', $str);
echo $str . '<br/>';
$str = preg_replace('/9(?=9*~)(?=.*(0))|~| ~0123456789$/', '$1', $str);
echo $str . '<br/>';
Run Code Online (Sandbox Code Playgroud)
输出:
0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909
0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909 ~0123456789
1 2 3 4 8 9 19~ 11 29~ 199~ 119~ 299~ 919~ 1999~ 1199~ 1919~ ~0123456789
1 2 3 4 8 9 10 11 20 100 110 200 910 1000 1100 1910
Run Code Online (Sandbox Code Playgroud)
是否有可能在一次替换?
没有.
如果没有,是否至少可以在给定上限的单个替换中,例如,数字高达9999?
没有.
你甚至不能用它们各自的继承者替换0到8之间的数字.匹配后,将此数字分组:
/([0-8])/
Run Code Online (Sandbox Code Playgroud)
你需要更换它.但是,正则表达式不对数字起作用,而是对字符串起作用.所以你可以用这个数字的两倍替换"数字"(或更好:数字),但是正则表达式引擎不知道它复制了一个包含数值的字符串.
即使你做某事(愚蠢),因为:
/(0)|(1)|(2)|(3)|(4)|(5)|(6)|(7)|(8)/
Run Code Online (Sandbox Code Playgroud)
这样正则表达式引擎"知道"如果组1匹配,数字'0'
匹配,它仍然无法进行替换.你不能命令正则表达式引擎使用数字来代替1组'1'
,组'2'
与数字'2'
等.当然,有些像PHP工具将让你定义了几个不同的图案与相应的替换字符串,但我得到的印象是不是你在想什么.