替换两个字符之间的每个实例

11
我有以下数据,其中{n}表示占位符。
{n}{n}A{n}{n}A{n}
{n}A{n}{n}{n}{n}A
{n}{n}A{n}A{n}{n}
{n}{n}{n}A{n}A{n}B
{n}A{n}{n}B{n}{n}
A{n}B{n}{n}{n}{n}

我想要用例如字母C替换在两个A字符之间的每个占位符。为此,我编写了以下正则表达式,并使用preg_replace函数。

$str = preg_replace('~(?<=A)(\{n\})*(?=A)~', 'C', $str);

问题在于它用一个 C 替换了两个A之间的所有实例。我该如何修复我的正则表达式或preg_replace调用,以将每个占位符的实例替换为C

这应该是我的输出。

{n}{n}ACCA{n}
{n}ACCCCA
{n}{n}ACA{n}{n}
{n}{n}{n}ACA{n}B
{n}A{n}{n}B{n}{n}
A{n}B{n}{n}{n}{n}

但目前它输出的是这个。

{n}{n}ACA{n}
{n}ACA
{n}{n}ACA{n}{n}
{n}{n}{n}ACA{n}B
{n}A{n}{n}B{n}{n}
A{n}B{n}{n}{n}{n}

在这种情况下会发生什么:A{n}A{n}A?是ACA{n}A还是ACACA - Casimir et Hippolyte
3个回答

8
您可以通过使用\G来解决这个问题。
$str = preg_replace('~(?:\G(?!\A)|({n})*A(?=(?1)++A))\K{n}~', 'C', $str);
\G 特性是一个锚点,可以匹配两个位置中的一个:字符串的起始位置或上一个匹配结束的位置。\K 转义序列重置报告的匹配的起始点,并且之前消费过的字符不再被包括在内。
为了减少回溯的次数,您可以使用更复杂的表达式:
$str = preg_replace('~\G(?!\A)(?:{n}
                      |A(?:[^A]*A)+?((?=(?:{n})++A)\K{n}
                      |(*COMMIT)(*F)))
                      |[^A]*A(?:[^A]*A)*?(?1)~x', 'C', $str);

7

稍微冗长但更易于理解的解决方案是使用初始表达式将文本分成组; 然后在每个组内应用各自的转换:

$text = preg_replace_callback('~(?<=A)(?:\{n\})*(?=A)~', function($match) {
    // simple replacement inside
    return str_replace('{n}', 'C', $match[0]);
}, $text);

我对表达式进行了微小的调整,使用(?:...)来消除不必要的内存捕获。


2
老实说,如果性能是一个问题,可能有更快的解决方案。 (+1) - hwnd

4
(?<=A){n}(?=(?:{n})*A)|\G(?!^){n}

你可以尝试这个。将其替换为 C。在此,您必须使用 \G 来断言位置,即在前一个匹配的末尾或字符串的开头(对于第一次匹配)。这样,您就可以在第一个匹配之后进行匹配。请参见演示。
这里首先匹配后面跟着 A 且后面也跟着 A{n},其中可以在中间包含 {n}。在捕获后,您使用 \G 重置为前一个匹配的结尾,并继续替换找到的 {n}。请查看演示:https://regex101.com/r/wU4xK1/7
$re = "/(?<=A){n}(?=(?:{n})*A)|\\G(?!^){n}/";
$str = "{n}{n}A{n}{n}A{n}\n{n}A{n}{n}{n}{n}A\n{n}{n}A{n}A{n}{n}\n{n}{n}{n}A{n}A{n}B\n{n}A{n}{n}B{n}{n}\nA{n}B{n}{n}{n}{n}";
$subst = "C";

$result = preg_replace($re, $subst, $str);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接