1
\$\begingroup\$

I've written code for a program that reads a text file, checking if there's any capitalized parts of speech of specific types (noun, adj, verb, adv) in the text and replace them with the user's chosen word for that category.

Below is an example of how it the result should be:

Original: "The ADJECTIVE, ADJECTIVE, and ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was unaffected by these events."

Output: "The big, bad and evil panda walked to the cafe and then slapped the employee. A nearby waiter was unaffected by these events."

It's quite lengthy. Is there anywhere in the code I can make it more succinct? Or a better way to go about this problem? Your input is much appreciated!

txt_file = open('readtext.txt')
txt_str = txt_file.read() 
print(txt_str) 
print()

#turn file content to list, store words to replace in a list
txt_list = txt_str.split()
replace_w = ['ADJECTIVE', 'NOUN', 'ADVERB', 'VERB']

def save_suffixprefix(letters):
    """return a list of non letters at 
    the beginning/end and reverse the list of letters provided"""
    non_letters = ''
    for char in letters:
        if not char.isalpha():
            non_letters += char
        else:
            break
    letters.reverse()
    return non_letters

for i, word in enumerate(txt_list):
    letters = list(word) #get a list of char in each word to collect suffix/prefix
    prefix = save_suffixprefix(letters)
    suffix = save_suffixprefix(letters)
    word = ''.join(letters).strip(prefix).strip(suffix) #construct a word of only letters

    # checking if it's a word that needs replacing, if so, ask for input,
    # reconstruct new word with prefix, suffix reattached and update the file content list
    if word in replace_w:
        replace_this = input(f"Enter a/an {replace_w[replace_w.index(word)]}: ")
        txt_list[i] = prefix + replace_this + suffix
print()
new_ver = ' '.join(txt_list) #make file content list string again
print(new_ver)
# write to file 
txt_file = open('readtext.txt', 'w')
txt_file.write(new_ver)
txt_file.close()
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

The problem presented is one that is more readily solved using regular expressions. I would create a regular expression that matched any of the words in your replace_words list:

ADJECTIVE|NOUN|ADVERB|VERB

See regular expression demo

If there is the possibility of one of the replace words, e.g. 'NOUN', appearing in the input text in a context such as 'This was ANNOUNCED yesterday.' and you do not want to match this occurrence, then to ensure that the replace word is not immediately preceded or followed by another letter we use the following regular expression that ensures our replace words must be on "word boundaries":

\bADJECTIVE\b|\bNOUN\b|\bADVERB\b|\bVERB\b

We can dynamically build the regular expression we need from any arbitrary replace_words list as follows:

import re

replace_words = ['ADJECTIVE', 'NOUN', 'ADVERB', 'VERB']
regex = re.compile('|'.join(replace_words))

Then we just need to call method re.sub specifying a function that will be called when the next word in replace_words is to be replaced. We pass to re.sub the substitution function to be called and the input string to be searched for substitutions. Our substitution function is passed a matchobj on which a call is made to retrieve Group 0 (i.e. the entire match). `The whole program then becomes just:

import re

FILE_NAME = 'readtext.txt'

# read input:
with open(FILE_NAME) as txt_file:
    txt_str = txt_file.read()
print(txt_str)
print()

replace_words = ['ADJECTIVE', 'NOUN', 'ADVERB', 'VERB']
regex = re.compile('|'.join(replace_words))
# Or the following if you want to enforce word boundaries:
#regex = re.compile('|'.join(map(lambda word: fr'\b{word}\b', replace_words)))

new_ver = regex.sub(lambda matchobj: input(f'Enter a/an {matchobj.group(0)}: '), txt_str)
print(new_ver)

# write to file
with open(FILE_NAME, 'w') as txt_file:
    txt_file.write(new_ver)

The entire program is just 3 lines long excluding any I/O-related code.

I have made these other changes:

  1. Rather than replicate the file name in two places, I have defined a "constant", FILE_NAME.
  2. Rather than having to do explicit calls to close on a file descriptor, I have used a context manager to ensure that the files are closed.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.