Python, splitting strings on middle characters with overlapping matches using regex

1.2k Views Asked by At

In Python, I am using regular expressions to retrieve strings from a dictionary which show a specific pattern, such as having some repetitions of characters than a specific character and another repetitive part (e.g. ^(\w{0,2})o(\w{0,2})$).

This works as expected, but now I'd like to split the string in two substrings (eventually one might be empty) using the central character as delimiter. The issue I am having stems from the possibility of multiple overlapping matches inside a string (e.g. I'd want to use the previous regex to split the string room in two different ways, (r, om) and (ro, m)).

Both re.search().groups() and re.findall() did not solve this issue, and the docs on the re module seems to point out that overlapping matches would not be returned by the methods.

Here is a snippet showing the undesired behaviour:

import re
dictionary = ('room', 'door', 'window', 'desk', 'for')
regex = re.compile('^(\w{0,2})o(\w{0,2})$')
halves = []
for word in dictionary:
    matches = regex.findall(word) 
    if matches:
        halves.append(matches)
2

There are 2 best solutions below

0
On BEST ANSWER

I am posting this as an answer mainly not to leave the question answered in the case someone stumbles here in the future and since I've managed to reach the desired behaviour, albeit probably not in a very pythonic way, this might be useful as a starting point from someone else. Some notes on how improve this answer (i.e. making more "pythonic" or simply more efficient would be very welcomed).

The only way of getting all the possible splits of the words having length in a certain range and a character in certain range of positions, using the characters in the "legal" positions as delimiters, both using there and the new regex modules involves using multiple regexes. This snippet allows to create at runtime an appropriate regex knowing the length range of the word, the char to be seek and the range of possible positions of such character.

dictionary = ('room', 'roam', 'flow', 'door', 'window', 
              'desk', 'for', 'fo', 'foo', 'of', 'sorrow')
char = 'o'
word_len = (3, 6)
char_pos = (2, 3)
regex_str = '(?=^\w{'+str(word_len[0])+','+str(word_len[1])+'}$)(?=\w{'
             +str(char_pos[0]-1)+','+str(char_pos[1]-1)+'}'+char+')'
halves = []
for word in dictionary:
    matches = re.match(regex_str, word)
    if matches:
        matched_halves = []
        for pos in xrange(char_pos[0]-1, char_pos[1]):
            split_regex_str = '(?<=^\w{'+str(pos)+'})'+char
            split_word =re.split(split_regex_str, word)
            if len(split_word) == 2:
                matched_halves.append(split_word)
        halves.append(matched_halves)

The output is:

[[['r', 'om'], ['ro', 'm']], [['r', 'am']], [['fl', 'w']], [['d', 'or'], ['do', 'r']], [['f', 'r']], [['f', 'o'], ['fo', '']], [['s', 'rrow']]]

At this point I might start considering using a regex just to find the to words to be split and the doing the splitting in 'dumb way' just checking if the characters in the range positions are equal char. Anyhow, any remark is extremely appreciated.

3
On

EDIT: Fixed.

Does a simple while loop work?

What you want is re.search and then loop with a 1 shift: https://docs.python.org/2/library/re.html

>>> dictionary = ('room', 'door', 'window', 'desk', 'for')
>>> regex = re.compile('(\w{0,2})o(\w{0,2})')
>>> halves = []
>>> for word in dictionary:
>>>     start = 0
>>>     while start < len(word):
>>>         match = regex.search(word, start)
>>>         if match:
>>>             start = match.start() + 1
>>>             halves.append([match.group(1), match.group(2)])
>>>         else:
>>>            # no matches left
>>>            break

>>> print halves
[['ro', 'm'], ['o', 'm'], ['', 'm'], ['do', 'r'], ['o', 'r'], ['', 'r'], ['nd', 'w'], ['d', 'w'], ['', 'w'], ['f', 'r'], ['', 'r']]