是否可以使用正则表达式替换来增加数字?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了是否可以使用正则表达式替换来增加数字?相关的知识,希望对你有一定的参考价值。

是否可以使用正则表达式替换来增加数字?当然不使用evaluated/function-based substitution

这个问题的灵感来自another one, where the asker wanted to increment numbers in a text editor。可能有更多的文本编辑器支持正则表达式替换,而不是支持全脚本编写的文本编辑器,因此如果存在正则表达式,则可以方便地使用正则表达式。

而且,我经常从聪明的解决方案中学到整齐的东西,几乎无用的问题,所以我很好奇。

假设我们只讨论非负十进制整数,即\d+

  • 是否有可能在一次替换?或者,有限数量的替换?
  • 如果不是,至少可以给出上限,例如数字高达9999?

当然,它是可行的,给定一个while循环(在匹配时替换),但我们在这里寻求无循环解决方案。

答案

对于我之前做过的一个特定实现,这个问题的主题让我很开心。我的解决方案恰好是两个替换,所以我会发布它。

我的实现环境是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#~01234567890。第一个0是在需要舍入9到10的情况下。 01234567890块用于递增。 “9 10”的示例文本是:

09~01234567890 010~01234567890

可以单独描述下一个正则表达式的各个部分,它们通过管道连接以减少替换计数:

s/\b0(?!9*~)/$2/g

在所有不需要舍入的数字前面选择“0”数字并将其丢弃。

s/([0-9])(?=9*~[0-9]*?\1([0-9]))/$2/g

(?=)是正向前瞻,\ 1是匹配组#1。所以这意味着匹配所有后跟9s的数字,直到'〜'标记然后转到查找表并找到该数字后面的数字。替换为查找表中的下一个数字。因此,当正则表达式引擎解析数字时,“09~”变为“19~”,然后变为“10~”。

s/~[0-9]*/$2/g

此正则表达式删除〜查找表。

另一答案

哇,事实证明这是可能的(尽管很难看)!

如果您没有时间或无法阅读整个解释,这里是执行此操作的代码:

$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的数字就足够了。数字成功递增,没有结转。

对于数字1to 8,接下来的8个替代方案完全相同。然后我们处理两个特殊情况。

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

当我们替换9时,我们不会丢弃增量标记,而是将其放置在我们生成的0的左侧。这(与周围环路相结合)足以实现结转传播。现在剩下一个特例。对于所有仅由9s组成的数字,我们最终会在数字前面加上~。这是最后一个替代方案:

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

如果我们遇到一个前面没有数字的~(因此是负面的后观),它必须一直通过一个数字,因此我们只需用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递增字符串中的每个整数。

另一答案

我设法让它在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. 如果在文本中找不到某个角色,那么就没有办法让它看起来神奇。所以首先我们在最后插入我们关心的字符。 s/$/ ~0123456789/ 例如, 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(或者如果所有都是1s则前置9),以及(2)“标记”9s的每个尾随组。 s/(?=\d)(?:([0-8])(?=.*\1(\d)\d*$)|(?=.*(1)))(?:(9+)(?=.*(~))|)(?!\d)/$2$3$4$5/g 例如,我们的示例变为: 1 2 3 4 8 9 19~ 11 29~ 199~ 119~ 299~ 919~ 1999~ 1199~ 1919~ ~0123456789
  3. 最后,我们(1)用9s替换每个“标记”的0s组,(2)删除~s,以及(3)删除最后的字符集。 s/9(?=9*~)(?=.*(0))|~| ~0123456789$/$1/g 例如,我们的示例变为: 1 2 3 4 8 9 10 11 20 100 110 200 910 1000 1100 1910

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
另一答案

是否有可能在一次替换?

没有。

如果不是,那么至少可以在给定上限的单个替换中,例如,数字高达9999?

没有。

你甚至不能用它们各自的继承者替换0到8之间的数字。匹配后,将此数字分组:

/([0-8])/

你需要更换它。但是,正则表达式不对数字起作用,而是对字符串起作用。所以你可以用这个数字的两倍替换“数字”(或更好:数字),但是正则表达式引擎不知道它复制了一个包含数值的字符串。

即使你做某事(愚蠢),因为:

/(0)|(1)|(2)|(3)|(4)|(5)|(6)|(7)|(8)/

这样正则表达式引擎“知道”如果组1匹配,数字'0'匹配,它仍然无法进行替换。你不能指示正则表达式引擎用数字'1'替换组1,用'2'数字组替换'2'等。当然,像PHP这样的工具会让你用相应的替换字符串定义几个不同的模式,但是我得到了印象不是你想的。

另一答案

仅通过正则表达式搜索和替换是不可能的。

你必须使用其他东西来帮助实现这一目标。您必须使用手头的编程语言来增加数字。

编辑:

作为Single Unix Specification的一部分,正则表达式定义没有提到支持评估aritmethic表达式或执行aritmethic操作的能力的正则表达式。


尽管如此,我知道一些风格(TextPad,Windows编辑器)允许您使用\i作为替换术语,它是搜索字符串被找到的次数的增量计数器,但它不会将找到的字符串计算或解析为数字也不允许添加数字。

另一答案

我需要将输出文件的索引从一个我无法修改的管道增加一个。经过一些搜索,我在这个页面上得到了一个点击。虽然读数很有意义,但它们实际上并没有为问题提供可读的解决方案。是的,只有正则表达式可以做到这一点;不,它不可理解。

在这里,我想使用Python提供一个可读的解决方案,以便其他人不需要重新发明轮子。我可以想象你们中的许多人可能最终得到了类似的解决方案。

我们的想法是将文件名分成三组,并格式化您的匹配字符串,以便增加的索引是中间组。然后可以只增加中间组,之后我们再将三个组合在一起。

import re
import sys
import argparse
from os import listdir
from os.path import isfile, join



def main():
    parser = argparse.ArgumentParser(description='index shift of input')
    parser.add_argument('-r', '--regex', type=str,
            help='regex match string for the index to be shift')
    parser.add_argument('-i', '--indir', type=str,
            help='input directory')
    parser.add_argument('-o', '--outdir', type=str,
            help='output directory')

    args = parser.parse_args()
    # parse input regex string
    regex_str = args.regex
    regex = re.compile(regex_str)
    # ta

以上是关于是否可以使用正则表达式替换来增加数字?的主要内容,如果未能解决你的问题,请参考以下文章

sql server中对字段使用正则表达式替换???

sql正则匹配连续增加数字

notepad++正则表达式使数字自动增加

正则表达式

使用正则表达式替换仅保留正斜杠和数字

是否有正则表达式来删除“。”的第一个实例?