Home > Software design >  Pull several substrings from an input using specific characters to find them
Pull several substrings from an input using specific characters to find them

Time:12-07

I need to make a user created madlib where the user would input a madlib for someone else to use. The input would be something like this:

The (^noun^) and the (^adj^) (^noun^)

I need to pull anything between (^ and ^) so I can use the word to code so I get another input prompt to complete the madlib.

input('Enter "word in-between the characters":')

This is my code right now

madlib = input("Enter (^madlib^):")
a = "(^"
b = "^)"
start = madlib.find(a)   len(a)
end = madlib.find(b)
substring = madlib[start:end]
def mad():
   if "(^" in madlib:
      substring = madlib[start:end]
      m = input("Enter "   substring   ":")
      mad = madlib.replace(madlib[start:end],m)
   return mad
print(mad())

What am I missing?

CodePudding user response:

You can use re.finditer() to do this fairly cleanly by collecting the .span() of each match!

import re

# collect starting madlib
madlib_base = input('Enter madlib base with (^...^) around words like (^adj^)): ')

# list to put the collected blocks of spans and user inputs into
replacements = []

# yield every block like (^something^) by matching each end and `not ^` inbetween
for match in re.finditer(r"\(\^([^\^] )\^\)", madlib_base):
    replacements.append({
        "span": match.span(),  # position of the match in madlib_base
        "sub_str": input(f"enter a {match.group(1)}: "),  # replacement str
    })

# replacements mapping and madlib_base can be saved for later!

def iter_replace(base_str, replacements_mapping):
    # yield alternating blocks of text and replacement
    # skip the replacement span from the text when yielding
    base_index = 0  # index in base str to begin from
    for block in replacements_mapping:
        head, tail = block["span"]       # unpack span
        yield base_str[base_index:head]  # next value up to span
        yield block["sub_str"]           # string the user gave us
        base_index = tail                # start from the end of the span

# collect the iterable into a single result string
# this can be done at the same time as the earlier loop if the input is known
result = "".join(iter_replace(madlib_base, replacements))

Demonstration

...
enter a noun: Madlibs
enter a adj: rapidly
enter a noun: house
...
>>> result
'The Madlibs and the rapidly house'
>>> replacements
[{'span': (4, 12), 'sub_str': 'Madlibs'}, {'span': (21, 28), 'sub_str': 'rapidly'}, {'span': (29, 37), 'sub_str': 'house'}]
>>> madlib_base
'The (^noun^) and the (^adj^) (^noun^)'

CodePudding user response:

Your mad() function only does one substitution, and it's only called once. For your sample input with three required substitutions, you'll only ever get the first noun. In addition, mad() depends on values that are initialized outside the function, so calling it multiple times won't work (it'll keep trying to operate on the same substring, etc).

To fix it, you need to make it so that mad() does one substitution on whatever text you give it, regardless of any other state outside of the function; then you need to call it until it's substituted all the words. You can make this easier by having mad return a flag indicating whether it found anything to substitute.

def mad(text):
    start = text.find("(^")
    end = text.find("^)")
    substring = text[start 2:end] if start > -1 and end > start else ""
    if substring:
        m = input(f"Enter {substring}: ")
        return text.replace(f"(^{substring}^)", m, 1), True
    return text, False


madlib, do_mad = input("Enter (^madlib^):"), True
while do_mad:
    madlib, do_mad = mad(madlib)

print(madlib)
Enter (^madlib^):The (^noun^) and the (^adj^) (^noun^)
Enter noun: cat
Enter adj: lazy
Enter noun: dog
The cat and the lazy dog
  • Related