Замените каждый экземпляр между двумя символами


У меня есть следующие данные ниже, где {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}

Я хотел бы заменить каждый экземпляр заполнителя между двумя символами A, например, буквой C. Я написал для этого следующее регулярное выражение и использую функцию preg_replace.

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

Проблема в том, что он заменяет все экземпляры между двумя A на один C. Как я мог бы исправить свое регулярное выражение или вызов 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}
Author: Unihedron, 2015-02-13

3 answers

Вы можете решить проблему, установив якорь с помощью \G.

$str = preg_replace('~(?:\G(?!\A)|({n})*A(?=(?1)++A))\K{n}~', 'C', $str);

Функция \G - это якорь, который может совпадать в одной из двух позиций; начало позиции строки или позиция в конце последнего совпадения. Escape-последовательность \K сбрасывает начальную точку сообщенного совпадения, и все ранее использованные символы больше не включаются.

Чтобы уменьшить количество возвратов, вы могли бы использовать более сложное выражение:

$str = preg_replace('~\G(?!\A)(?:{n}
                      |A(?:[^A]*A)+?((?=(?:{n})++A)\K{n}
                      |(*COMMIT)(*F)))
                      |[^A]*A(?:[^A]*A)*?(?1)~x', 'C', $str);
 8
Author: hwnd, 2015-02-14 06:48:50

Несколько более подробное, но более простое в использовании решение состоит в том, чтобы использовать начальное выражение для разбиения текста на группы; затем примените индивидуальное преобразование внутри каждой группы:

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

Я немного изменил выражение, чтобы избавиться от захвата памяти, в котором нет необходимости, с помощью (?:...).

 7
Author: Ja͢ck, 2015-02-13 04:40:30
(?<=A){n}(?=(?:{n})*A)|\G(?!^){n}

Вы можете попробовать это. Заменить на C. Здесь вы должны использовать \G, чтобы утвердить позицию в конце предыдущего совпадения или в начале строки для первого совпадения.

Чтобы вы могли соответствовать после вашего первого матча. Смотрите демонстрацию.

Https://regex101.com/r/wU4xK1/7

Здесь сначала вы сопоставляете {n}, за которым стоит A, и A после него, который может иметь {n} между ними. После захвата вы используете \G для сброса до конца предыдущего матча и впоследствии продолжайте заменять {n} найденный.

$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);
 4
Author: vks, 2015-02-13 04:34:58