2
\$\begingroup\$

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 = []
        badletters = []
        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"))
print(is_stressful("Headlamp, wastepaper bin and supermagnificently"))
print(is_stressful("I neeed advice!!!!"))

Thanks for any tips!

\$\endgroup\$
3
\$\begingroup\$

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"))
    print(is_stressful("Headlamp, wastepaper bin and super-magnificently"))
    print(is_stressful("I neeed advice!!!!"))

The output:

True
True
False
True
\$\endgroup\$
7
  • \$\begingroup\$ 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. \$\endgroup\$
    – spence
    Nov 10 '19 at 22:49
  • \$\begingroup\$ 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. \$\endgroup\$
    – spence
    Nov 10 '19 at 22:55
  • \$\begingroup\$ @spence, you're welcome, glad to help \$\endgroup\$ Nov 11 '19 at 7:56
  • 1
    \$\begingroup\$ @Graipher, thanks for a spelling hint "quantify" (I also noted "recognise") \$\endgroup\$ Nov 11 '19 at 9:14
  • 1
    \$\begingroup\$ @Graipher A frozenset, in that case. There's a good reason to make it immutable. \$\endgroup\$
    – Gloweye
    Nov 11 '19 at 9:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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