Skip to main content
4 of 5
added 4 characters in body
Yuushi
  • 11.1k
  • 2
  • 31
  • 66

Code length is one factor in readability, but it isn't the only factor. "Is this code easy to read and modify" is a more important question to ask.

It looks like you've gotten a method skeleton here, which kind of leads you down the path of stuffing everything in one function, which is a bit of a shame. Let's look at what we actually need here.

  1. Length of at least 6
  2. One lowercase English character
  3. One uppercase English character
  4. One digit
  5. One special character

What if we split this up into a bunch of separate functions, which each do one thing. We'll just write the function names down for now, and leave the implementation blank.

def needs_lower(text):
    pass

def needs_upper(text):
    pass

def needs_digit(text):
    pass

def needs_extra_chars(text):
    pass

def needs_special(text):
    pass

Now, Python has a string module that is (semi)deprecated, but that has some still useful things, namely, string.ascii_lowercase, string.ascii_uppercase, and string.digit. We can use these in the implementations for some of our functions. Let's just write them as simply as possible for now.

import string

def needs_lower(text):
    for char in text:
        if char in string.ascii_lowercase:
            return 0
    return 1

def needs_upper(text):
    for char in text:
        if char in string.ascii_uppercase:
            return 0
    return 1

def needs_digit(text):
    for char in text:
        if char in string.digits:
            return True
    return False

There's quite a bit of repetition here that we can factor out.

def needs_any(text, values):
    for char in text:
        if char in values:
            return True
    return False

Then our previous functions can just call this:

def needs_lower(text):
    return needs_any(text, string.ascii_lowercase)

And similarly for the needs_upper and needs_digit.

In fact, we can do this with our special characters as well:

SPECIAL_CHARS = '!@#$%^&*()-+'

def needs_special(text):
    return needs_any(text, SPECIAL_CHARS)

The last function we need to implement is needs_extra_chars:

MINIMUM_LENGTH = 6

def needs_extra_chars(text):
    if len(text) >= MINIMUM_LENGTH:
        return 0
    return MINIMUM_LENGTH - len(text)

Now we can stitch these all together within minimumNumber:

def minimumNumber(n, password):
    # I'm not really sure why there's a n passed in as well?
    test_functions = [needs_lower, needs_upper,
                      needs_digit, needs_special,
                      needs_extra_chars]
    extra_required = sum([test_fn(password) for test_fn in test_functions])
    return extra_required

The benefit of doing this is the fact that we can easily add extra checks. What if a new requirement comes along? We can just create a new function an add it to test_functions; the amount of existing code we have to touch is minimal.

Just to drive home the point about size vs readability, here is a (slightly) code golfed version:

import string

def minimumRequired(password):
    if len(password) < 6:
        return 6 - len(password)
    unique_pw = frozenset(password)
    char_sets = (frozenset(string.ascii_lowercase), frozenset(string.ascii_uppercase),
                 frozenset(string.digits), frozenset('!@#$%^&*()-+'))
    required_extra = 0
    for char_set in char_sets:
        if not char_set & unique_pw:
            required_extra += 1
    return required_extra

This (probably - I haven't really tested it) satisfies the requirements, but how do I make changes to it? Is it obvious how it does what it does (I'd argue not really).

Yuushi
  • 11.1k
  • 2
  • 31
  • 66