是否有可能使用正则expression式replace数字?
是否有可能使用正则expression式replace数字? 当然,不使用基于评估/函数的replace 。
这个问题的另一个启发,提问者想在文本编辑器中增加数字 。 支持正则expression式replace的文本编辑器可能比支持正式脚本的文本编辑器更多,所以如果存在正则expression式可能会很方便。
另外,我经常从巧妙的解决方法中学习整洁的东西,几乎无用的问题,所以我很好奇。
假设我们只讨论非负的十进制整数,即\d+
。
-
是否有可能在一个单一的替代? 或者,有限的替代?
-
如果没有,是否至less有可能给出一个上限 ,例如数字高达9999?
当然,给定一个while循环是可行的(在匹配的情况下replace),但是我们要在这里寻找一个空洞的解决scheme。
这个问题的主题让我感到很开心,因为我之前做过一个特定的实现。 我的解决scheme恰好是两个替代,所以我会张贴它。
我的实现环境是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
拉开它的解释:
s/\b([0-9]+)\b/0$1~01234567890/g
对于每个号码(#)用0#〜01234567890replace它。 前面的0是在需要舍入9到10的情况下。 01234567890块用于递增。 “9 10”的示例文本是:
09~01234567890 010~01234567890
下一个正则expression式的各个部分可以单独描述,它们通过pipe道连接以减lessreplace计数:
s/\b0(?!9*~)/$2/g
select所有不需要舍入的数字前面的“0”数字并丢弃。
s/([0-9])(?=9*~[0-9]*?\1([0-9]))/$2/g
(?=)是积极的前瞻,\ 1是比赛组#1。 所以这意味着匹配所有的数字后跟9,直到'〜'标记,然后去查找表,并find这个数字后面的数字。 用查找表中的下一个数字replace。 因此,当正则引擎parsing数字时,“09〜”变成“19〜”,然后变成“10〜”。
s/~[0-9]*/$2/g
这个正则expression式删除〜查找表。
哇,原来这是可能的(尽pipe丑陋)!
如果你没有时间,或者无法阅读整个解释,这里是代码:
$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;
现在我们开始吧。
所以首先,就像其他人提到的那样,即使循环它也是不可能的(即,如何将相应的增量插入到一个数字中)。 但是,如果您先准备string,则可以循环使用一个replacestring。 这是我使用PHP的演示实现。
我用这个testingstring:
$str = '0 1 2 3 4 5 6 7 8 9 10 11 12 13 19 20 29 99 100 139';
首先,我们通过添加一个标记字符(我使用~
标记要增加的所有数字,但是您应该使用一些疯狂的Unicode字符或者ASCII字符序列,这些字符绝对不会出现在您的目标string中。
$str = preg_replace("/\d+/", "$0~", $str);
由于我们一次只能更换一位数字(从右到左),我们只是在每个完整的数字之后加上标记字符。
现在这里来主要的黑客。 我们在string的末尾添加了一些“查找”(也用一个不会出现在string中的独特字符分隔;为简单起见,我使用了#
)。
$str = preg_replace("/$/", "#123456789~0", $str);
我们将用它来replace相应的后继数字。
现在循环:
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);
好的,发生了什么事? 对于每个可能的数字,匹配模式都有一个select。 这将数字映射到后继者。 以第一个替代scheme为例:
0~(.*#.*(1))
这将匹配任何0
跟随我们的增量标记~
,然后它匹配一切,直到我们的作弊分隔符和相应的继任者(这就是为什么我们把每一个数字)。 如果你看一下更换,这将被$2$1
取代(这将是1
,然后是我们在~
之后匹配的所有东西,将其放回原位)。 请注意,我们在这个过程中删除~
。 将数字从0
增加到1
就足够了。 数字成功递增,没有结转。
接下来的8个选项对于数字1
到8
完全相同。 然后,我们照顾两个特殊情况。
9~(.*#.*(~0))
当我们replace9
,我们不会删除增量标记,而是将其放在我们的结果0
的左侧。 这(与周围环路结合)足以实现结转传播。 现在还有一个特殊情况。 对于所有仅由9
秒组成的数字,我们将以数字前面的~
结束。 这是最后的select是:
(?<!\d)~(.*#.*(1))
如果我们遇到了一个没有数字的前面(因此是负面的倒序),它一定是通过一个数字完成的,因此我们简单地用1
代替它。 我认为我们甚至不需要消极的后顾之忧(因为这是被检查的最后一个select),但是这样感觉更安全。
关于整个模式(?|...)
简短说明。 这确保我们总是在相同的引用$1
和$2
find替代的两个匹配(而不是string中更大的数字)。
最后,我们添加DOTALL
修饰符,使其与包含换行符的string一起工作(否则,只有最后一行中的数字才会增加)。
这使得一个相当简单的replacestring。 我们先写$2
(我们在其中捕获了后继者,可能还有结转标记),然后我们把所有我们匹配的东西放在$1
地方。
而已! 我们只需要从string的末尾删除我们的黑客,我们就完成了:
$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
所以我们完全可以用正则expression式来完成。 而唯一的循环,我们总是使用相同的正则expression式。 我相信这是非常接近我们可以不使用preg_replace_callback()
。
当然,如果我们的string中有小数点的数字,这将会造成可怕的结果。 但是,这可能是由第一次准备replace照顾。
更新:我刚刚意识到,这种方法立即扩展到任意增量(不只是+1
)。 只需更改第一个replace。 您追加的数量等于您应用于所有数字的增量。 所以
$str = preg_replace("/\d+/", "$0~~~", $str);
会使string中的每个整数增加3
。
我设法让它工作在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
说明
让~
是一个特殊的字符, 不应该出现在文本的任何地方。
-
如果一个angular色没有在文本中find,那么就没有办法使它看起来神奇。 所以我们首先插入我们最关心的angular色。
s/$/ ~0123456789/
例如( 点击这里refiddle ),
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)递增最后的非
9
(或者如果全部是9
秒,则预先加1
),以及(2)“标记”每个9
秒的尾随组。s/(?=\d)(?:([0-8])(?=.*\1(\d)\d*$)|(?=.*(1)))(?:(9+)(?=.*(~))|)(?!\d)/$2$3$4$5/g
例如,( 点击这里refiddle ),我们的例子变成:
1 2 3 4 8 9 19~ 11 29~ 199~ 119~ 299~ 919~ 1999~ 1199~ 1919~ ~0123456789
-
最后,我们(1)用
0
s代替9
s中的每个“标记”组,(2)删除~
,并且(3)删除最后的字符集。s/9(?=9*~)(?=.*(0))|~| ~0123456789$/$1/g
例如,( 点击这里refiddle ),我们的例子变成:
1 2 3 4 8 9 10 11 20 100 110 200 910 1000 1100 1910
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/>';
输出:
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
是否有可能在一个单一的替代?
没有。
如果没有,至less有可能在一个单一的替代上限,例如数字高达9999?
没有。
你甚至不能replace0到8之间的数字与他们各自的继任者。 一旦你匹配,并分组这个数字:
/([0-8])/
你需要更换它。 但是,正则expression式并不是在数字上操作,而是在string上操作。 所以你可以用这个数字的两倍来代替“数字”(或者更好:数字),但是正则expression式引擎并不知道它是复制一个包含数值的string。
即使你会这样做(愚蠢):
/(0)|(1)|(2)|(3)|(4)|(5)|(6)|(7)|(8)/
所以正则expression式引擎“知道”如果组1匹配,数字'0'
匹配,它仍然不能做replace。 您不能指示正则expression式引擎用数字'1'
replace组1,用数字'1'
replace组'2'
等等。当然,像PHP这样的工具可以让你定义一些不同的模式并用相应的replacestring,但我得到的印象是不是你在想什么。
仅靠正则expression式search和replace是不可能的。
你必须使用别的东西来帮助实现这一点。 您必须使用手头的编程语言来增加数字。
编辑:
作为单一Unix规范的一部分,正则expression式定义没有提及支持对aritmethicexpression式进行评估的正则expression式或执行aritmethic操作的能力。
尽pipe如此,我知道一些风格(TextPad,Windows编辑器)允许你使用\i
作为替代项,它是一个增量计数器,有多less次searchstring被find,但它不会评估或分析find的string一个数字,也不允许添加一个数字。