# Simple Email Subject Filtering (python)

This challenge took me forever. I spent a lot of time messing with regular expressions, but couldn't get it to work. I ended up coming up with this abomination. If anybody wants to take the time I'm interested in simpler ways to accomplish this task. (I'm a brand new pythoner)

def is_stressful(subj):
"""
recognise a stressful email subject
we are looking for any of: all uppercase, or ending in 3 !!!, or
containing 'help', 'asap', or 'urgent' despite have extraneous spellings
"""

import string

flagged_words = ['help', 'asap', 'urgent']

if subj[-3:] == '!!!':  # check for ending in at least 3 !!!
return True
if subj.isupper():   # check for uppercase
return True

stripped = "".join(c for c in subj if c not in string.punctuation) # get rid of confusing characters
wordlist = stripped.lower().split(' ') # make the list to check

for word in wordlist:  # first easy check, everything is spelled correctly
if word in flagged_words:
return True
for word in wordlist:  # start the annoying check, getting rid of extra letters in flagged words
r = 0  # our word list counter
getgood = []
while r < len(flagged_words):  # start the loop
for i, l in enumerate(word):  # going through each character
if l in flagged_words[r]:  # checking for good letters
if l not in getgood:
getgood.append(l)  # no repeats except for 'a' in asap (don't know a good way for this)
elif l == 'a' and getgood.count('a') < 2:
getgood.append(l)
else:
badletters.append(False)  # make sure we don't spell flagged words accidentally
r += 1  # go to the next word in list
i = 0  # reset letter counter
if ''.join(getgood) in flagged_words and all(badletters):  # our final check
return True
getgood = []  # reset the loop
badletters = []  # reset the loop
return False


My tests:

print(is_stressful("H!E!L!P! its urGent asAP"))
print(is_stressful("asaaap"))


Thanks for any tips!

Improvements with a new solution:

• import string. If the function is a top-level commonly used function - better to move that import at the top of enclosing module.
Though in my proposed solution string won't be used.

• flagged_words. Instead of generating a list of flagged words on every function's call - make it a constant with immutable data structure defined in the outer scope:

FLAGGED_WORDS = ('help', 'asap', 'urgent')

• empty subj. To avoid multiple redundant checks/conditions on empty subj argument, if such would be passed in, a better way is handling an empty string at start:

subj = subj.strip()
if not subj:
raise ValueError('Empty email subject!')

• if subj[-3:] == '!!!' and if subj.isupper() checks lead to the same result.
That's a sign for applying Consolidate conditional expression technique - the conditions are to be combined with logical or operator

• the last for ... while ... for traversal looks really messy and over-complicated.
When trying to untangle that, your test cases allowed me to make an assumption that the crucial function (besides of trailing !!! chars and all upper-cased letters) should catch:

• exact word match with any of the flagged words, like urGent asAP (test case #1)
• exact word match with repetitive allowed chars like asaaap (test case #2)

and should not allow strings that contain only words which combine both allowed and unallowed chars like Headlamp or wastepaper (though they contain he..l..p, .as....ap..) (test case #3)

Instead o going into a mess of splitting/loops I'd suggest a complex regex solution that will cover both exact word matching and exact matching with repetitive allowed chars cases.
The underlying predefined pattern encompasses the idea of quantifying each char in each flagged word like h+e+l+p+ with respect for word boundaries \b:

import re

FLAGGED_WORDS = ('help', 'asap', 'urgent')

def quantify_chars_re(words):
"""Adds + quantifier to each char for further use in regex patterns"""
return [''.join(c + '+' for c in w) for w in words]

RE_PAT = re.compile(fr"\b({'|'.join(quantify_chars_re(FLAGGED_WORDS))})\b", re.I)

def is_stressful(subj):
"""
Recognize a stressful email subject
we are looking for any of: all uppercase, or ending in 3 !!!, or
containing 'help', 'asap', or 'urgent' despite have extraneous spellings
"""

subj = subj.strip()
if not subj:
raise ValueError('Empty email subject!')

if subj.isupper() or subj[-3:] == '!!!' or RE_PAT.search(subj):
return True

return False

if __name__ == "__main__":
print(is_stressful("H!E!L!P! its urGent asAP"))
print(is_stressful("asaaap"))


The output:

True
True
False
True

• That messy complicated loop I made was exactly why I posted this. I'm glad you were at least able to decipher what it was trying to accomplish. (my test cases) the regex was my initial attempt, but I need a lot more practice understanding. Your code is very clean and concise. I learned a lot from it. – spence Nov 10 '19 at 22:49
• Your quantify_chars is a very elegant solution. What was difficult for me with regex is returning proper results on the flagged words that were spelled with extra letters. – spence Nov 10 '19 at 22:55
• @spence, you're welcome, glad to help – RomanPerekhrest Nov 11 '19 at 7:56
• @Graipher, thanks for a spelling hint "quantify" (I also noted "recognise") – RomanPerekhrest Nov 11 '19 at 9:14
• @Graipher A frozenset, in that case. There's a good reason to make it immutable. – Gloweye Nov 11 '19 at 9:23