Regular Expression split w/ Lookbehind loses second half

Viewed 49

I have a string that contains a number of keywords. I would like to split the string into a list of those keywords (but keep the keywords because they identify what the following data means)

Take the following string for example:

test_string = "ªttypmp3pfilfDjTunes/DJ Music/(I've Had) The Time Of My Life.mp3tsng<(I've Had) The Time Of My Lifetart:Bill Medley & Jennifer Warnes"

the important keywords are "ttyp", "pfil", "tsng", "tart". I would like to split the file so the output looks:

split_test_string = ["ª","ttypmp3","pfilfDjTunes/DJ Music/(I've Had) The Time Of My Life.mp3","tsng<(I've Had) The Time Of My Life","tart:Bill Medley & Jennifer Warnes"].

I've been researching regular expressions, and I think this expression would work, but when tested in Python, I end up losing the part that I want to keep. According to the Python re.split documents, this should work.

Checkout my regex calculator: https://regex101.com/r/FOlgv8/1

Note: I'm trying to get the first part to work. Then I'll add the rest of the keywords using |.

regex = r'(?=ttyp).*'

This is my example code:

import re
regex = r'(?=ttyp).*'

split_test_string = re.split(regex, test_string)
print(f"Results: {split_test_string}")

Console Output:

Results: ['ª', '']

I've tried positive lookahead and positive lookback with no luck. I could just use a literal 'ttyp' but then I lose the keyword.

Any help would be appreciated, I've been researching, trial and erroring (mostly erroring) for hours now.

3 Answers

Here ya go:

re.split("(?=ttyp|pfil|tsng|tart)", test_string)

The reason yours didn't work is that you split by .*, meaning you capture everything after the separator and treat it as the seperator itself (and thus throw it).

Here is a regex based solution using re.findall:

test_string = "ªttypmp3pfilfDjTunes/DJ Music/(I've Had) The Time Of My Life.mp3tsng<(I've Had) The Time Of My Lifetart:Bill Medley & Jennifer Warnes"
keywords = ["ttyp", "pfil", "tsng", "tart"];
regex = r'(?:' + '|'.join(keywords) + r'|$)';
matches = re.findall(r'.+?' + r'(?=' + regex + r')', test_string)
print(matches)

This prints:

['\xc2\xaa', 'ttypmp3', "pfilfDjTunes/DJ Music/(I've Had) The Time Of My Life.mp3",
 "tsng<(I've Had) The Time Of My Life", 'tart:Bill Medley & Jennifer Warnes']

The strategy here is to match all content up to, but not including, anyone of the 4 keywords which you identified. Note that we need to make the Python script UTF-8 friendly in this case as your have some UTF-8 characters.

from re import split
regex = '((?:ttyp|pfil|tsng|tart).*?)(?=ttyp|pfil|tsng|tart|$)'
test_string = "ªttypmp3pfilfDjTunes/DJ Music/(I've Had) The Time Of My Life.mp3tsng<(I've Had) The Time Of My Lifetart:Bill Medley & Jennifer Warnes"
matches = [x for x in split(regex, test_string) if x]
print(matches)

The way it works is that it matches the keyword you want, followed by the minimal number of additional characters needed to hit another keyword, or the end of the string. Afterwards, I filtered out the empty string from the list, so it would match what you wanted. You can see for yourself what happens if you don't do that.

Related