是否有可能使用正则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个选项对于数字18完全相同。 然后,我们照顾两个特殊情况。

 9~(.*#.*(~0)) 

当我们replace9 ,我们不会删除增量标记,而是将其放在我们的结果0的左侧。 这(与周围环路结合)足以实现结转传播。 现在还有一个特殊情况。 对于所有仅由9秒组成的数字,我们将以数字前面的~结束。 这是最后的select是:

 (?<!\d)~(.*#.*(1)) 

如果我们遇到了一个没有数字的前面(因此是负面的倒序),它一定是通过一个数字完成的,因此我们简单地用1代替它。 我认为我们甚至不需要消极的后顾之忧(因为这是被检查的最后一个select),但是这样感觉更安全。

关于整个模式(?|...)简短说明。 这确保我们总是在相同的引用$1$2find替代的两个匹配(而不是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 

说明

~是一个特殊的字符, 应该出现在文本的任何地方。

  1. 如果一个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 
  2. 接下来,对于每个数字,我们(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 
  3. 最后,我们(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一个数字,也不允许添加一个数字。