5
\$\begingroup\$

This is the criteria:

Its length is at least 6. It contains at least one digit. It contains at least one lowercase English character. It contains at least one uppercase English character. It contains at least one special character. The special characters are: !@#$%^&*()-+

My code is working for all test cases but I'm afraid it seems way longer than necessary. I'm trying to trim it down.

import math
import os
import random
import re
import sys

# Complete the minimumNumber function below.
def minimumNumber(n, password):
    # Return the minimum number of characters to make the password strong
    flag = 0
    if len(password) >= 6:        
        passRegex = re.compile(r"^(?=.*[\d])")
        mo = passRegex.search(password)
        if mo is None:
            flag+=1
        passRegex = re.compile(r"^(?=.*[A-Z])")
        mo = passRegex.search(password)
        if mo is None:
            flag+=1
        passRegex = re.compile(r"^(?=.*[a-z])")
        mo = passRegex.search(password)
        if mo is None:
            flag+=1
        passRegex = re.compile(r'[!@#$%^&*()-+]')
        mo = passRegex.search(password)
        if mo is None:
            flag+=1
        return flag
    else:
        if any(x in "0123456789" for x in password) is False:
            flag+=1
        if any(x in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" for x in password) is False:
            flag+=1
        if any(x in "abcdefghijklmnopqrstuvwxyz" for x in password) is False:
            flag+=1    
        if any(x in "!@#$%^&*()-+" for x in password) is False:
            flag+=1    
        if (flag+len(password)<6):
            return 6-len(password)
        if (flag+len(password)>=6):
            return flag


if __name__ == '__main__':

    n = int(input())

    password = input()

    answer = minimumNumber(n, password)

Any advice is appreciated.

\$\endgroup\$

1 Answer 1

10
\$\begingroup\$

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 lower_required(text):
    pass

def upper_required(text):
    pass

def digits_required(text):
    pass

def length_required(text):
    pass

def special_required(text):
    pass

Now, Python has a string module that is (semi)deprecated (much of what was in it is now on string objects themselves), 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 lower_required(text):
    for char in text:
        if char in string.ascii_lowercase:
            return 0
    return 1

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

def digit_required(text):
    for char in text:
        if char in string.digits:
            return 0
    return 1

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

def num_required(text, characters):
    if any((x in characters for x in text)):
        return 0
    return 1

Then our previous functions can just call this:

def lower_required(text):
    return num_required(text, string.ascii_lowercase)

And similarly for the upper_required and digit_required.

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

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

def special_required(text):
    return num_required(text, SPECIAL_CHARS)

The last function we need to implement is length_required:

MINIMUM_LENGTH = 6

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

Now we can stitch these all together within minimumNumber (note that Python functions should be named using snake_case, so it should be minimum_number here - but I assume this function name was given to you, so I won't harp on about it too much).

def minimumNumber(text):
    test_functions = [lower_required, upper_required,
                      digits_required, special_required]
    min_chars_required = sum([test_fn(text) for test_fn in test_functions])
    missing_length = length_required(text)
    if missing_length > min_chars_required:
        return missing_length
    return min_chars_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).

\$\endgroup\$
8
  • \$\begingroup\$ string.ascii_lower should be string.ascii_lowercase, minimumNumber shouldn't have any uppercase letter (as well as a more descriptive name IMHO) and your golfed version returns too fast, for example with 'aaaaa'. It should return 3, not just 1. \$\endgroup\$ Commented Sep 17, 2018 at 8:48
  • 1
    \$\begingroup\$ Do you have any reference for string being deprecated any time soon? Because I use those constants quite often (in Code Review answers). \$\endgroup\$
    – Graipher
    Commented Sep 17, 2018 at 10:50
  • 1
    \$\begingroup\$ All the needs_* functions can be rewritten quite nicely with the any() built-in \$\endgroup\$
    – Ludisposed
    Commented Sep 17, 2018 at 11:29
  • 1
    \$\begingroup\$ Halfway through this answer you seem to switch from "return 0 if the password doesn't need one of the specified characters, and 1 otherwise" to "return True if the password has one of the specified characters, and False otherwise". To me these don't seem equivalent. Additionally, your final check doesn't seem like it will return the correct result for passwords which are too short but also need specific characters, since those specific characters already need to be added which should reduce the number of generic characters needed to get the minimum length. \$\endgroup\$ Commented Sep 17, 2018 at 15:15
  • 1
    \$\begingroup\$ @KamilDrakari Yeah, the True/False was an oversight from when I was originally writing my answer. I've edited this with a few fixes and renamed some things. \$\endgroup\$
    – Yuushi
    Commented Sep 17, 2018 at 16:10

Your Answer

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

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