Trying to create function to extract slice between special characters

Viewed 85

I have code that I thought would work, but it does not. In particular, how can I handle the case where there is only a begin or end phrase within the text. See my code below. Thanks!

import re
def extract(text, begin, end):
   result1 = re.search(begin, text)
   if result1 is None:
      index1 = " "
   else:
      index1 = text.find(begin) + len(begin)
   result2 = re.search(end, text)
   if result2 is None:
      index2 = " "
   else:
      index2 = text.find(end)
   return text[index1:index2]

print(extract("Eat an <apple> each day", "<", ">"))

print(extract("Oh [/b] no", "[b]", "[/b]"))

#First case works as expected and prints "apple". I am expecting "Oh" to print for the second case, but it's not returning anything. Why not and how do I fix it? enter image description hereenter image description here

4 Answers

You really don't need to use regex here. In fact, your problem in part is due to the fact that you use regex. [ and ] are special characters in regex, and it essentially means "match any single character that is listed" between the two brackets. So your string will try to match a b for begin and either / or b for end. We can do this without using regex using just the .find method, which will actually return -1 if it couldn't find anyting.

def extract(text, begin, end):
    index1 = text.find(begin)
    if index1 != -1:
        index1 += len(begin)
    # start next search at index1, or 0 if begin not found
    index2 = text.find(end, index1 if index1 != -1 else 0)
    print(index1, index2)
    if index2 != -1:
        # end string found!
        return text[index1 if index1 != -1 else 0:index2]
    elif index1 != -1:
        # begin string found!
        return text[index1:index2  if index2 != -1 else len(text)]

print(extract("Eat an <apple> each day", "<", ">"))
# "apple"
print(extract("Oh [/b] no", "[b]", "[/b]"))
# "Oh "
print(extract("Oh [b] no", "[b]", "[/b]"))
# " no"

uses indexing

s = "abcacbAUG[GAC]UGAfjdalfd"
start = s.find("[") + len("[")
end = s.find("]")
substring = s[start:end]
print(substring)

prints GAC

The main problem is your second regexp. [b] and [/b] does different thing than you think:

  • [b] chooses single character b
  • [/b] chooses single character b or single character /

You have to escape the square brackets if you want to match [/b] in the text.

I also modified the index evaluation. Since you already searched the string and have result, you can use result.span() to get the indices (start-end as a tuple) of the match.

import re
def extract(text, begin, end):
   result1 = re.search(begin, text)
   if result1 is None:
      index1 = 0
   else:
      index1 = result1.span()[1]
   result2 = re.search(end, text)
   if result2 is None:
      index2 = len(text)
   else:
      index2 = result2.span()[0]
   return text[index1:index2]

print(extract("Eat an <apple> each day", "<", ">"))
print(extract("Oh [/b] no", r"\[b\]", r"\[/b\]"))

If you want to use regex, you can look for:

  • optionally (but if possible) as many characters as possible followed by the begin sequence (non-capturing group)
  • then the characters you want to capture (any number of any non-newline character, although as few as possible)
  • then the end sequence or end of line (another non-capturing group)

You need to escape the sequences (see use of re.escape), and not doing so may be part of your current problem, e.g. the [b] is treated as a character class.

def extract(text, begin, end):

    begin = re.escape(begin)
    end = re.escape(end)

    reg = f'(?:.*{begin})?(.*?)(?:{end}|$)'
    match = re.match(reg, text)
    if match:
        return match.group(1)
Related