# Recursively replace string placeholders with parameterized phrases

I've written a python script to replace certain placeholders in a text with defined phrases. Some of the phrases might contain placeholders themselves, so the script needs to recursively replace them. Additionally the placeholders might allow parameters that need to be inserted at certain positions.

The current approach feels a little slow, because I'm iterating over the text much. It also fails (and crashes) if not enough parameters are provided for a parameterizable placeholder.

import re

parameters_regex = "(?:$(.*)$)?"

def expand_message(message):
"""Recursivly replaces any occurencys of building blocks with its text."""
for key, value in phrases.items():
match = re.search(key + parameters_regex, message)
if match:
replacement = ""
if (match.group(1)):
parameters = match.group(1).split(",")
replacement = value.format(*parameters)
else:
replacement = value
replacement = expand_message(replacement)
message = message.replace(match.group(0), replacement)
return message

def format_numerators(message):
"""Replaces the numerator placeholders with the actual numerator."""
numerator = 1
while ('#.)' in message):
message = message.replace('#.)', str(numerator) + ".)", 1)
numerator+=1
return message

def format_message(message):
"""Formats and returns the message."""
message = expand_message(message)
message = format_numerators(message)
return message


This is how I'm testing it at the moment:

phrases = { }
phrases["bb:WORKDAY"] = "bb:GET_UP\nbb:BRUSH_TEETH\nbb:GO_TO_WORK[train]\nbb:GO_HOME"
phrases["bb:FRIDAY"] = "bb:WORKDAY\nbb:GO_OUT[cinema,John]\nbb:GO_HOME"
phrases["bb:WEEKEND"] = "bb:GET_UP\nbb:GO_OUT[park,Mary]\nbb:GO_HOME"

phrases["bb:GET_UP"] = "#.) Get out of bed."
phrases["bb:BRUSH_TEETH"] = "#.) Brush your teeth."
phrases["bb:GO_TO_WORK"] = "#.) Take the {} and go to work."
phrases["bb:GO_HOME"] = "#.) Go home."
phrases["bb:GET_UP"] = "#.) Get out of bed."
phrases["bb:GO_OUT"] = "#.) Go to the {} with {}."
phrases["bb:GET_UP"] = "#.) Get out of bed."

print(format_message("bb:WORKDAY"))
print("\n")
print(format_message("bb:FRIDAY"))
print("\n")
print(format_message("bb:WEEKEND"))


1.) Get out of bed.
3.) Take the train and go to work.
4.) Go home.

1.) Get out of bed.
3.) Take the train and go to work.
4.) Go home.
5.) Go to the cinema with John.
6.) Go home.

1.) Get out of bed.
2.) Go to the park with Mary.
3.) Go home.

• I'm concerned with, "It also fails (and crashes) if not enough parameters are provided for a parameterizable placeholder." is this not intended? If so, your code may be off-topic here as we only allow code that works as you intend, however slow it may be. – Peilonrayz Aug 14 '17 at 8:57
• The code itself works as intended as I currently assume that the right number of parameters is provided. – DIF Aug 14 '17 at 9:00

What if you would use re.sub() with a replacement function until there are no unfulfilled placeholders left. Something along these lines:

PHRASE_PATTERN = re.compile(r"(bb:\w+)(?:$(.*)$)?")

def expand_message(message):
"""Recursively replaces any occurrences of building blocks with its text."""
while "bb:" in message:
message = PHRASE_PATTERN.sub(lambda group: phrases[group[1]].format(*group[2].split(","))
if group[2] else phrases[group[1]],
message)
return message


This way you don't need to iterate over all the available phrases and only handling the phrases used in a message. Should scale much better.

• Thank you! I was not aware of re.sub() - seems to be the perfect tool for my problem – DIF Aug 16 '17 at 5:33