Может ли Regex использоваться для этой конкретной манипуляции с строкой?

Мне нужно заменить символ (скажем) x символом (скажем) P в строке, но только если он содержится в указанной подстроке. Пример делает это более ясным:

axbx'cxdxe'fxgh'ixj'k  -> axbx'cPdPe'fxgh'iPj'k

Предположим, для простоты, что цитаты всегда попадают в пары.

Очевидным способом является просто обрабатывать строку один символ за раз (простой подход с использованием конечных автоматов),
однако, мне интересно, можно ли использовать регулярные выражения для выполнения всей обработки за один раз.

Мой целевой язык - это С#, но, я думаю, мой вопрос относится к любому языку, имеющему встроенную или библиотечную поддержку для регулярных выражений.

Ответ 1

Я смог сделать это с помощью Python:

>>> import re
>>> re.sub(r"x(?=[^']*'([^']|'[^']*')*$)", "P", "axbx'cxdxe'fxgh'ixj'k")
"axbx'cPdPe'fxgh'iPj'k"

Что это значит, это использование несоответствующего соответствия (? =...), чтобы проверить, что символ x находится в кавычной строке. Он ищет некоторые неквотированные символы до следующей цитаты, затем ищет последовательность как одиночных символов, так и кавычек групп символов до конца строки.

Это зависит от вашего предположения, что котировки всегда сбалансированы. Это также не очень эффективно.

Ответ 2

Я преобразовал код Python Грега Hewgill в С#, и он сработает!

[Test]
public void ReplaceTextInQuotes()
{
  Assert.AreEqual("axbx'cPdPe'fxgh'iPj'k", 
    Regex.Replace("axbx'cxdxe'fxgh'ixj'k",
      @"x(?=[^']*'([^']|'[^']*')*$)", "P"));
}

Этот тест прошел.

Ответ 3

Хитрость заключается в том, чтобы использовать группу, не связанную с захватом, для соответствия части строки после соответствия (символ x), который мы ищем. Попытка сопоставить строку до x будет находить только первое или последнее вхождение, в зависимости от того, используются ли неживые кванторы. Здесь идея Грега перенесена в Tcl с комментариями.

set strIn {axbx'cxdxe'fxgh'ixj'k}
set regex {(?x)                     # enable expanded syntax 
                                    # - allows comments, ignores whitespace
            x                       # the actual match
            (?=                     # non-matching group
                [^']*'              # match to end of current quoted substring
                                    ##
                                    ## assuming quotes are in pairs,
                                    ## make sure we actually were 
                                    ## inside a quoted substring
                                    ## by making sure the rest of the string 
                                    ## is what we expect it to be
                                    ##
                (
                    [^']*           # match any non-quoted substring
                    |               # ...or...
                    '[^']*'         # any quoted substring, including the quotes
                )*                  # any number of times
                $                   # until we run out of string :)
            )                       # end of non-matching group
}

#the same regular expression without the comments
set regexCondensed {(?x)x(?=[^']*'([^']|'[^']*')*$)}

set replRegex {P}
set nMatches [regsub -all -- $regex $strIn $replRegex strOut]
puts "$nMatches replacements. "
if {$nMatches > 0} {
    puts "Original: |$strIn|"
    puts "Result:   |$strOut|"
}
exit

Отпечатки:

3 replacements. 
Original: |axbx'cxdxe'fxgh'ixj'k|
Result:   |axbx'cPdPe'fxgh'iPj'k|

Ответ 4

#!/usr/bin/perl -w

use strict;

# Break up the string.
# The spliting uses quotes
# as the delimiter.
# Put every broken substring
# into the @fields array.

my @fields;
while (<>) {
    @fields = split /'/, $_;
}

# For every substring indexed with an odd
# number, search for x and replace it
# with P.

my $count;
my $end = $#fields;
for ($count=0; $count < $end; $count++) {
    if ($count % 2 == 1) {
        $fields[$count] =~ s/a/P/g;
    }    
}

Разве этот кусок не выполнит эту работу?

Ответ 5

Более общее (и более простое) решение, которое позволяет не парные кавычки.

  • Найти строку с кавычками
  • Замените 'x' на 'P' в строке

    #!/usr/bin/env python
    import re
    
    text = "axbx'cxdxe'fxgh'ixj'k"
    
    s = re.sub("'.*?'", lambda m: re.sub("x", "P", m.group(0)), text)
    
    print s == "axbx'cPdPe'fxgh'iPj'k", s
    # ->   True axbx'cPdPe'fxgh'iPj'k
    

Ответ 6

Не с обычным регулярным выражением. Регулярные выражения не имеют "памяти", поэтому они не могут различать "внутренние" или "внешние" кавычки.

Вам нужно что-то более мощное, например, используя gema, это было бы несправедливо:

'<repl>'=$0
repl:x=P

Ответ 7

Аналогичная дискуссия о сбалансированном тексте заменяет: Можно ли использовать регулярные выражения для соответствия вложенным шаблонам?

Хотя вы можете попробовать это в Vim, но он работает хорошо, только если строка находится в одной строке и есть только одна пара.

:%s:\('[^']*\)x\([^']*'\):\1P\2:gci

Если есть еще одна пара или даже неуравновешенный, то это может потерпеть неудачу. Таким образом, я включил флаг c a.k.a. в команду ex.

То же самое можно сделать с sed без взаимодействия - или с awk, чтобы вы могли добавить какое-то взаимодействие.

Одно из возможных решений состоит в том, чтобы разбить линии на пары ', тогда вы можете сделать это с помощью решения vim.

Ответ 8

Pattern:     (?s)\G((?:^[^']*'|(?<=.))(?:'[^']*'|[^'x]+)*+)x
Replacement: \1P
  • \G — Якорь каждого совпадения в конце предыдущего или начало строки.
  • (?:^[^']*'|(?<=.)) — Если он находится в начале строки, совпадайте с первой цитатой.
  • (?:'[^']*'|[^'x]+)*+ — Сопоставьте любой блок некотируемых символов или любые (не кавычные) символы до "x".

Один прогон по исходной строке, за исключением одиночного символа.

Ответ 9

Извините, что нарушал ваши надежды, но для этого вам нужны пусковые автоматы. Здесь больше информации: Pushdown Automaton

Короче говоря, регулярные выражения, которые являются машинами с конечным состоянием, могут читать и не иметь памяти, тогда как автомат pushdown имеет стек и управляет возможностями.

Изменить: правописание...