2

There is a task that says to replace NOUN words with any noun word possible while using regex substitution but issue is that, there are two of these words and each should be replaced separately.

The string sentence is like this: 'ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was unaffected by these events.'

Output should be something like this: 'Cute panda walked to the pool and then jumped. A nearby giraffe was unaffected by these events.'

As you see there are two NOUN words and each should be replaced with different words.

My code for this is like this:

import re

TextFileContent ='ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was unaffected by these events.'

SpecialRegexes_NOUN = re.compile('NOUN')

if SpecialRegexes_NOUN.search(TextFileContent) != None:
    ForGroup2 = input('Enter a noun: ')
    ForGroup3 = input('Enter a noun: ')
    TextFileContent = re.sub('NOUN', ForGroup2, TextFileContent, 1)
    TextFileContent = re.sub('NOUN', ForGroup3, TextFileContent, 1)

print(TextFileContent)

When I run, get error warning saying:

Warning (from warnings module):
  File "D:\MyPythonScripts\Mad Libs.py", line 25
    TextFileContent = re.sub('NOUN', ForGroup3, TextFileContent, 1)
DeprecationWarning: 'count' is passed as positional argument
10
  • 2
    It looks like that is just a deprecation i.e. not actually a fatal error. Does the print work? Commented Nov 19 at 20:18
  • 3
    Try swapping the 1 to count=1 to see if that fixes the deprecation warning. Commented Nov 19 at 20:20
  • 3
    So what is a problem here? What is the question? Commented Nov 19 at 20:52
  • 1
    Please re-read the minimal reproducible example guidance on posting code. If you have problem with code - make sure to specify if it fails to run OR fails with exception when run OR results don't match your expectations. In either case include that information in the post. Remove all inputs from the code and use constant values, remove conditional (if) statements and keep minimal code that demonstrate the problem. (Note that on SO questions expected to show example code that demonstrates the single problem, reviewing of complete working code may be suitable for Code Review) Commented Nov 19 at 22:22
  • 1
    Thanks @sln. I don't do Python myself, so I fished my tiny bit of knowledge out of the Python manual. I assume the OP's code works, and they're mistaking the deprecation message for a failure. I assume from the code provided they want count=1. Commented Nov 20 at 0:03

3 Answers 3

4

UPDATED SOLUTION (2025 NOV 21): The previous solution is rigid and it does not work if a replacement string input preceding another replacement string input (in this case the first replacement string) includes a literal NOUN string(s). This below solution, uses re.finditer() to first get the indexes of all the literal NOUN strings. And, the requests matching replacement strings. It then replaces literal NOUN strings with the replacement text from user input, starting from the end of the text string. Also added word boundary syntax (\b) into the regex to make sure we do not replace the NOUN in strings like PRONOUN, etc.

PYTHON:

import re

text_file_content ='ADJECTIVE panda walked to the [1]:NOUN:[1]  and then VERB. A nearby [2]:NOUN:[2] was unaffected by these events.ADJECTIVE panda walked to the [3]:NOUN:[3] and then VERB. A nearby [4]:NOUN:[4] was unaffected by these events.ADJECTIVE panda walked to the [5]:NOUN:[5] and then VERB. A nearby [6]:NOUN:[6] was unaffected by these events.ADJECTIVE panda walked to the [7]:NOUN:[7] and then VERB. A nearby [8]:NOUN:[8] was unaffected by these events.'

print(text_file_content)

nouns_found = re.search(r'\bNOUN\b', text_file_content)

if nouns_found != None:

    # Find all NOUNs
    matches = re.finditer(r'\bNOUN\b', text_file_content)
    
    noun_indexes = list()   # Initialize

    # Get start and end index for each NOUN
    for match in matches:
        index_span = match.span()    # (start_index, end_index)
        noun_indexes.append(index_span)

    number_of_nouns = len(noun_indexes)     # NOUN count (from text)

    # Reverse the index list with elements (start_index, end_index)
    noun_indexes_reversed = sorted(noun_indexes, key=lambda x: x[0], reverse=True)

    print(f"Number of NOUN strings: {number_of_nouns}")

    replacement_strings = list()    # Initialize

    # Get the replacements from user:
    for i in range(number_of_nouns): 
        input_string = input(f'Enter a noun ({str(i+1)}): ')
        replacement_strings.append((i, input_string))
    
    # Reverse the order of the replacement strings
    replacement_strings_reversed = sorted(replacement_strings, key=lambda x: x[0], reverse=True)    

    # Replace the NOUN with the matching replacement, starting from the end of the string. 
    for i, string in enumerate(replacement_strings_reversed):
        
        text_file_content = text_file_content[:noun_indexes_reversed[i][0]] + string[1] + text_file_content[noun_indexes_reversed[i][1]:]

    # Print result
    print(text_file_content)

OUTPUT (TERMINAL) Text before and after:

ADJECTIVE panda walked to the [1]:NOUN:[1]  and then VERB. A nearby [2]:NOUN:[2] was unaffected by these events.ADJECTIVE panda walked to the [3]:NOUN:[3] and then VERB. A nearby [4]:NOUN:[4] was unaffected by these events.ADJECTIVE panda walked to the [5]:NOUN:[5] and then VERB. A nearby [6]:NOUN:[6] was unaffected by these events.ADJECTIVE panda walked to the [7]:NOUN:[7] and then VERB. A nearby [8]:NOUN:[8] was unaffected by these events.

Number of NOUN strings: 8

Enter a noun (1): (STRING 1)
Enter a noun (2): (STRING 2)
Enter a noun (3): (STRING 3)
Enter a noun (4): (STRING 4)
Enter a noun (5): (STRING 5)
Enter a noun (6): (STRING 6)
Enter a noun (7): (STRING 7)
Enter a noun (8): (STRING 8)

ADJECTIVE panda walked to the [1]:(STRING 1):[1]  and then VERB. A nearby [2]:(STRING 2):[2] was unaffected by these events.ADJECTIVE panda walked to the [3]:(STRING 3):[3] and then VERB. A nearby [4]:(STRING 4):[4] was unaffected by these events.ADJECTIVE panda walked to the [5]:(STRING 5):[5] and then VERB. A nearby [6]:(STRING 6):[6] was unaffected by these events.ADJECTIVE panda walked to the [7]:(STRING 7):[7] and then VERB. A nearby [8]:(STRING 8):[8] was unaffected by these events.

OLD ANSWER: This solution works, however, it is rigid and does not allow literal NOUN to be in the user input. (In this case the first user input). Provided a dynamic solution above that allows any string input from user.

count=1 fixes the issue.

The count parameter requires a keyword argument.

SYNTAX (Corrected 2025 NOV 21)

 re.sub(pattern, repl, string, *, count=0, flags=0)

Python Docs Ref: https://docs.python.org/3/library/re.html#re.sub (1)

(1) The relevant quote from the linked reference: Deprecated since version 3.13: Passing count and flags as positional arguments is deprecated. In future Python versions they will be keyword-only parameters.

PYTHON

import re

TextFileContent ='ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was unaffected by these events.'

SpecialRegexes_NOUN = re.compile('NOUN')

if SpecialRegexes_NOUN.search(TextFileContent) != None:
    ForGroup2 = input('Enter a noun: ')
    ForGroup3 = input('Enter a noun: ')
    TextFileContent = re.sub('NOUN', ForGroup2, TextFileContent, count=1)
    TextFileContent = re.sub('NOUN', ForGroup3, TextFileContent, count=1)

print(TextFileContent)

OUTPUT (TERMINAL)

Enter a noun: BALLOON     # User input
Enter a noun: BUTTERFLY   # User input
ADJECTIVE panda walked to the BALLOON and then VERB. A nearby BUTTERFLY was unaffected by these events
Sign up to request clarification or add additional context in comments.

6 Comments

Note that there's still a bug here - if ForGroup2 contains NOUN as a a substring, then that substring will be replaced by ForGroup3. Better to perform the substitutions in reverse order (i.e. re.sub('NOUN', ForGroup3, TextFileContent, count=2), then re.sub('NOUN', ForGroup2, TextFileContent, count=1)).
Oh, I see what you are saying. Yeah, it would have to be a whole different solution to manage a possible literal NOUN (all uppercase) string as part of the first user entry. The reverse order works when doing index replacements, however, the issue I see with count=2 is that it will replace the first two matches (instead of the second match only which is what we would want) with the replacement string (ForGroup3). So in this example output, the output would be "ADJECTIVE panda walked to the BUTTERFLY and then VERB. A nearby BUTTERFLY was unaffected by these ev
Oh yes, count is, well, a count. I was mixing up with sed, where the count indicates which single replacement to make. I've identified a bug (which, in fairness, is present in the original), but didn't present the correct fix.
That could work, but you would need to do re.sub('NOUN', ForGroup3, TextFileContent, count=2) then re.sub(ForGroup3, ForGroup2, TextFileContent, count=1). The edge case here would be that ForGroup3 already exists in the initial string.
@Toby Speight I updated the answer to a solution that uses re.finditer() (instead of using re.sub()) and replacing indexes, which will allow any strings from the user, including literal NOUN(s). Thank you for the insight. I learned that this cool way of using re.sub() can be 'dangerous' and will require 'extra attention'. The index way seems headache free. Thnx :)
The relevant quote from the linked reference: Deprecated since version 3.13: Passing count and flags as positional arguments is deprecated. In future Python versions they will be keyword-only parameters. That explains why the "syntax" section you show isn't (yet) re.sub(pattern, repl, string, *, count=0, flags=0).
1

Instead of calling re.sub() multiple times, you can call it with a function as the replacement, and it can iterate through a list of replacement words each time it's called.

import re

nouns = ["pool", "giraffe"]
noun_index = -1

def get_next_noun(_):
    global noun_index
    noun_index += 1
    return nouns[noun_index]

TextFileContent ='ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was unaffected by these events.'

SpecialRegexes_NOUN = re.compile('NOUN')
TextFileContent = SpecialRegexes_NOUN.sub(get_next_noun, TextFileContent)

print(TextFileContent)

5 Comments

Fantastic solution! AND: And, we can put the replacement string input inside the def get_next_noun to dynamically get the matching replacements strings from user input: <code>def get_next_noun(_): global noun_index noun_index += 1 input_string = input(f'Enter a noun ({str(noun_index)}): ') return input_string </code> Thank you for sharing. Thank you, @Barmar! :)
In that case you don't need noun_index.
Yes, you do not need the noun_index. I just thought it would be a nice touch to keep "global" track of the noun index so we can include noun_index in the input string. This way the user knows the index of the NOUN we are requesting replacement for via user input.
We can eliminate the global and make this function reusable by capturing the list: def list_func(seq): return lambda _, terms=seq.copy(): terms.pop(0) and using it to convert the list to a function that returns each element in turn: SpecialRegexes_NOUN.sub(list_func(nouns), TextFileContent). I wrote code to generalise to accept a dict of lists, keyed on 'NOUN', 'VERB', ... etc, but it's somewhat too large for a comment, and too much of a tangent to be an answer to the question.
@TobySpeight Indeed, if I were implementing a general Mad Libs application, I would use a dict like that. I also thought about the lambda but I didn't want to complicate the answer for a beginner.
-1

It is possible to do this without using a regex. The idea is to replace the first instance of 'NOUN' with '{0}' , the second with '{1}' and so forth, so that the string can simply be formated:

TextFileContent = 'ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was unaffected by these events.'

i = 0
while 'NOUN' in TextFileContent:
    TextFileContent = TextFileContent.replace('NOUN', '{' + str(i) + '}', 1)
    i += 1

print(TextFileContent)
print(TextFileContent.format('bird', 'bee', 'cat'))

Note that here, the fact that 3 replacers were defined while there were only 2 NOUNs was not an issue.

2 Comments

The task description specifically says to do this using regexp replacement.
Fair enough. I won't be updating my answer because rich neadle's answer is already as good as it gets.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.