Дополнительные символы в конце замененного текста

В PHP и Java я применил /^[^\pL]*|[^\pL]*$/ к -A-, и я получил *A**. Я применил симметричный шаблон и получил асимметричный результат! Зачем? Интересно, почему его вывод не *A*?

Шаблон говорит, что каждая вещь, кроме буквы в конце строки, должна быть заменена на *, она также жадная и должна заменить все небуквенные файлы вместе.

Заметка Alos в RegexBuddy Я получаю *A*, что я ожидаю.

Обновление: Я упростил вопрос, чтобы сосредоточить мою главную заботу.

Ответы

Ответ 1

#^[^\pL]+|[^\pL]+$#u

Замените * на +. Использование * в сочетании с $ работает не так, как можно было бы ожидать. В странном следствии того, как работает двигатель регулярного выражения, X*$ найдет два совпадения для X*. Использование + исправляет его.

Объяснение

[^\pL]*$

Посмотрим на эту часть регулярного выражения, часть, которая работает не так, как ожидалось. Почему он помещает два * в конец некоторых строк?

  • Рассмотрим третью примерную строку ---A--- после замены первого набора тире:

    *A---$
    
  • Механизм regex находит соответствие для регулярного выражения здесь:

    *A---$
      ^
    
  • И заменяет "---" звездочкой:

    *A*$
      ^
    
  • Затем он перемещает свой внутренний курсор вправо от строки замены.

    *A*$
       ^
    
  • Он начинается с этой позиции курсора и ищет другое совпадение. И он находит одно! Он находит "" — пустую строку! "" состоит из 0-или более не-букв ([^\pL]*), и он привязан в конце строки ($), поэтому это действительное совпадение. Он нашел пустую строку, конечно, но это разрешено.

    Это неожиданно, потому что он снова привязал якорь $. Разве это не так? Он не должен совпадать с $ снова, не так ли? Ну, на самом деле, это должно и делать. Он может соответствовать $ снова, потому что $ не является фактическим символом во входной строке — это утверждение с нулевой шириной. Первая замена не "израсходована". $ разрешено сопоставлять дважды.

  • И, следовательно, он "заменяет" пустую строку "" звездочкой. Вот почему вы получаете две звездочки.

    *A**$
       ^
    
  • Если двигатель regex вернется к шагу 4, он найдет еще одну пустую строку и добавит еще одну звездочку. В концептуальном плане существует бесконечное число пустых строк. Чтобы этого избежать, двигатель не позволяет начать следующий матч в том же положении, что и предыдущий. Это правило не позволяет войти в бесконечный цикл.

Ответ 2

Правильное регулярное выражение будет выглядеть следующим образом:

$arr = preg_replace('#^[^\pL]+|[^\pL]+$#','*', 
           array('A','-A-','---A---','-+*A*+-','------------A------------'));

Примечание + вместо *. Это даст результат:

Array
(
    [0] => A
    [1] => *A*
    [2] => *A*
    [3] => *A*
    [4] => *A*
)

PS: Обратите внимание, что первый элемент останется неизменным из-за отсутствия символа не-альфы до и после A.

Ответ 3

Дайте этому пробегу:
Объяснение дается, как после кода, так и внутри тела кода - как комментарии.

<?php
class String
{
    private $str;
    public function __construct($str)
    {
        $this->str=$str;
    }
    public function replace($regex,$replacement)
    {
        return preg_replace($regex,$replacement,$this->str);
    }
}

function String($str)
{
    return new String($str);
}

echo String('A')->replace('/^[^\pL]*|[^\pL]*$/','*').'<br />';//Outputs *A*
 //Why does this output *A* and not A?
 //Because it successfully matches an empty string
 //The easiest way to test for the presence of an empty string is like so:
echo String('A')->replace('//','*').'<br />';//Outputs *A*
 //The engine begins by placing its internal pointer before the string like so:
 // A
 //^
 //It then tests the regular expression for the empty string ""
 //Most regular expressions will fail this test. But in our case matches it successfully.
 //Since we are preforming a search and replace the "" will get replaced by a "*" character
 //Then the internal pointer advances to the next character after its successful match
 // A
 // ^
 //It tests our regular expression for the A character and it fails.
 //Since we are performing a search and replace the searched "A" portion remains unchanged as "A"
 //The internal pointer advances to the next character
 // A
 //  ^
 //It tests our regular expression for the empty string ""
 //Again, most regular expressions will fail this test. But since ours successfully matched it,
 //The "" portion will get replaced by "*"
 //The engine then returns our output:
 //*A*
echo '<hr />';
 //If we wanted to replace the A character too, we'd do this:
echo String('A')->replace('/|A/','*').'<br />';//Outputs ***
 //Or we could do:
echo String('A')->replace('/.*?/','*').'<br />';//Outputs ***
 //Thus we see for a 1 character string the engine will test for the empty spaces "" before and after the character as well
 //For a 19 character string it tests for all the gaps between each character like so:
echo String('19 character string')->replace('//','*').'<br />';//Outputs *1*9* *c*h*a*r*a*c*t*e*r* *s*t*r*i*n*g*
 //For an empty string it would match once successfully like so:
echo String('')->replace('//','*').'<br />';//Outputs *

echo String('A')->replace('/^[^\pL]*|[^\pL]*$/','*');//Outputs *A*

Почему указанный выше выход *A*, а не A?
Поскольку это регулярное выражение будет успешно соответствовать пустой строке "".
Такое же поведение наблюдается с использованием пустого регулярного выражения, например:
echo String('A')->replace('//','*');//Outputs *A*

Теперь я объясняю, почему реализация механизма регулярных выражений производит эти странные результаты. Впоследствии вы поймете, что они совсем не такие странные, но на самом деле правильное поведение.

Двигатель начинает с размещения его внутреннего указателя перед строкой: >

  A
_ _ _
^

Так как указатель указывает на пустую строку "", она затем проверяет ее на наше регулярное выражение.
Большинство регулярных выражений не пройдут этот тест, потому что минимальное количество символов, необходимых для удовлетворения регулярного выражения, обычно является одним или несколькими. Но в нашем случае совпадение успешно, потому что 0 символов является действительным совпадением с нашим регулярным выражением.
Поскольку мы создаем поиск и замену, "" будет заменен символом "*".
Затем внутренний указатель продвигается к следующему символу после его успешного совпадения:

  A
_ _ _
  ^

Он проверяет наше регулярное выражение для символа "A" и не работает.
Поскольку мы выполняем поиск и замену, искомая часть "A" остается неизменной как "A"
Внутренний указатель продвигается к следующему символу:

  A
_ _ _
    ^

Он проверяет наше регулярное выражение для пустой строки ""
Опять же, большинство регулярных выражений не пройдут этот тест.
Но поскольку наше регулярное выражение успешно соответствует ему, часть "" заменяется на "*"
Затем двигатель завершает цикл через нашу строку "A" и возвращает наш вывод: "*A*"

Если бы мы хотели заменить символ A, мы бы это сделали:
echo String('A')->replace('/|A/','*');//Outputs ***

Или мы могли бы сделать:
echo String('A')->replace('/.*?/','*').'<br />';//Outputs ***

Таким образом, мы видим, что для 1 символьной строки движок будет проверяться на "" до и после символа.

Для 19-символьной строки он проверяет все промежутки между каждым символом так:
echo String('19 character string')->replace('//','*');
//Outputs *1*9* *c*h*a*r*a*c*t*e*r* *s*t*r*i*n*g*

Для пустой строки он будет успешно соответствовать так:
echo String('')->replace('//','*');//Outputs *

Это завершает мое объяснение. Чтобы исправить ваше регулярное выражение, сделайте так, как ранее предлагалось и используйте:
/^[^\pL]+|[^\pL]+$/
Это создаст минимальное количество символов, необходимое для удовлетворения регулярного выражения, тем самым обойдя нежелательное поведение.

Как последнее замечание, если кто-то задается вопросом, что \pL делает в регулярных выражениях, это в основном означает: сопоставить любой буквенный символ (в отличие от числа или символа). Здесь объясняется: http://www.php.net/manual/en/regexp.reference.unicode.php

Ответ 4

/^[^\pL]*|[^\pL]*$/  
['A','-A-','---A---','-+*A*+-','------------A------------']

Возможно, я неправильно понимаю вопрос или регулярное выражение, но похоже, что он соответствует одному из двух вариантов

Вариант 1: он соответствует /^ началу строки новой строки или строки. он затем совпадает с символом, который не является буквой ноль или более раз

поэтому теоретически -A, =A, -, =-+_+_==-=~````[email protected]#$A или даже [email protected]# будут соответствовать этому.

Вариант 2: он соответствует тому, что не является буквой ноль или более раз, а затем совпадает с концом строки или строки