Replace a character enclosed with lowercase letters

Viewed 157

All the examples I've found on stack overflow are too complicated for me to reverse engineer.

Consider this toy example

s = "asdfasd a_b dsfd"

I want s = "asdfasd a'b dsfd"

That is: find two characters separated by an underscore and replace that underscore with an apostrophe

Attempt:

re.sub("[a-z](_)[a-z]","'",s)
# "asdfasd ' dsfd"

I thought the () were supposed to solve this problem?

Even more confusing is the fact that it seems that we successfully found the character we want to replace:

re.findall("[a-z](_)[a-z]",s)
#['_']

why doesn't this get replaced?

Thanks

3 Answers

Use look-ahead and look-behind patterns:

re.sub("(?<=[a-z])_(?=[a-z])","'",s)

Look ahead/behind patterns have zero width and thus do not replace anything.

UPD:

  • The problem was that re.sub will replace the whole matched expression, including the preceding and the following letter.
  • re.findall was still matching the whole expression, but it also had a group (the parenthesis inside), which you observed. The whole match was still a_b
  • lookahead/lookbehind expressions check that the search is preceded/followed by a pattern, but do not include it into the match.
  • another option was to create several groups, and put those groups into the replacement: re.sub("([a-z])_([a-z])", r"\1'\2", s)

When using re.sub, the text to keep must be captured, the text to remove should not.

Use

re.sub(r"([a-z])_(?=[a-z])",r"\1'",s)

See proof.

EXPLANATION

NODE                     EXPLANATION
--------------------------------------------------------------------------------
  (                        group and capture to \1:
--------------------------------------------------------------------------------
    [a-z]                    any character of: 'a' to 'z'
--------------------------------------------------------------------------------
  )                        end of \1
--------------------------------------------------------------------------------
  _                        '_'
--------------------------------------------------------------------------------
  (?=                      look ahead to see if there is:
--------------------------------------------------------------------------------
    [a-z]                    any character of: 'a' to 'z'
--------------------------------------------------------------------------------
  )                        end of look-ahead

Python code:

import re
s = "asdfasd a_b dsfd"
print(re.sub(r"([a-z])_(?=[a-z])",r"\1'",s))

Output:

asdfasd a'b dsfd

The re.sub will replace everything it matched .

There's a more general way to solve your problem , and you do not need to re-modify your regular expression.

Code below:

import re


s = 'Data: year=2018, monthday=1, month=5, some other text'
reg = r"year=(\d{4}), monthday=(\d{1}), month=(\d{1})"


r = "am_replace_str"
def repl(match):
    _reg = "|".join(match.groups())
    return re.sub(_reg, r,match.group(0)) if _reg else r

# 
re.sub(reg,repl, s)

output: 'Data: year=am_replace_str, monthday=am_replace_str, month=am_replace_str, some other text'

Of course, if your case does not contain groups , your code may like this :

import re


s = 'Data: year=2018, monthday=1, month=5, some other text'
reg = r"year=(\d{4}), monthday=(\d{1}), month=(\d{1})"


r = "am_replace_str"
def repl(match):
    _reg = "|".join(match.groups())
    return re.sub(_reg, r,match.group(0))

# 
re.sub(reg,repl, s)
Related