1
\$\begingroup\$

This is a Leetcode problem -

A password is considered strong if below conditions are all met -

1. It has at least 6 characters and at most 20 characters.

2. It must contain at least one lowercase letter, at least one uppercase letter, and at least one digit.

3. It must NOT contain three repeating characters in a row (...aaa... is weak, but ...aa...a... is strong, assuming other conditions are met).

Write a function strong_password_checker(s), that takes a string s as input, and returns the MINIMUM change required to make s a strong password. If s is already strong, return 0.

Insertion, deletion or replacements of any one character are all considered as one change.

Here is my solution to this challenge -

def strong_password_checker(s: str) -> int:
    def has_lower(s):
        for c in s:
            if c.islower(): return 1
        return 0

    def has_upper(s):
        for c in s:
            if c.isupper(): return 1
        return 0

    def has_digits(s):
        for c in s:
            if c.isnumeric(): return 1
        return 0

    def find_repeats(s):
        i = 0
        j = 0
        repeats = []
        while i < len(s) - 1:
            if s[i+1] == s[i]:
                i += 1
                continue
            if (i - j + 1) > 2: repeats.append(i - j + 1)
            i += 1
            j = i
        if (i - j + 1) > 2: repeats.append(i - j + 1)
        return repeats

    def repeats_after_delete(reps, d):
        if d >= sum([r - 2 for r in reps]):
            return []
        reps = sorted(reps, key=lambda d: d%3)
        while d > 0:
            for i in range(len(reps)):
                if reps[i] < 3:
                    continue
                r = reps[i] % 3 + 1
                reps[i] -= min(r, d)
                d -= r
                if d <= 0:
                    break
        return [r for r in reps if r > 2]

    def num_repeats_change(repeats):
        return sum([r // 3 for r in repeats])

    total_changes = 0
    format_changes = (1 - has_lower(s)) + (1 - has_upper(s)) + (1 - has_digits(s))
    repeats = find_repeats(s)
    if len(s) < 6:
        repeat_change = num_repeats_change(repeats)
        total_changes = max([6 - len(s), format_changes, repeat_change])
    elif len(s) > 20:
        repeats = repeats_after_delete(repeats, len(s) - 20)
        repeat_change = num_repeats_change(repeats)
        total_changes = len(s) - 20 + max([repeat_change, format_changes])
    else: 
        repeat_change = num_repeats_change(repeats)
        total_changes = max([repeat_change, format_changes])
    return total_changes

Here are some example outputs -

#print(strongPasswordChecker("aaaaaaaaaaaaAsaxqwd1aaa"))

>>> 6

#Explanation - aa1aa1aa1aAsaxqwd1aa (just an example - delete three characters, add three digits to replace consecutive a's)

#print(strongPasswordChecker("aaaaa"))

>>> 2

#Explanation - aaAaa1 (just an example - add a character (so the length is at least 6), add an uppercase letter to replace consecutive a's)

#print(strongPasswordChecker("aAsaxqwd2aa"))

>>> 0

#Explanation - A strong password (all conditions are met)

Here are the times taken for each output -

%timeit strongPasswordChecker("aaaaaaaaaaaaAsaxqwd1aaa")
>>> 18.7 µs ± 1.75 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit strongPasswordChecker("aaaaa")
>>> 5.05 µs ± 594 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit strongPasswordChecker("aAsaxqwd2aa")
>>> 7.19 µs ± 469 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

So, I would like to know whether I could make this program shorter and more efficient.

Any help would be highly appreciated.

\$\endgroup\$
  • 2
    \$\begingroup\$ It really is too bad they called this a strong password checker. It might've been in the 80's, but certainly not anymore. \$\endgroup\$ – l0b0 May 29 at 8:55
2
\$\begingroup\$

You can make it shorter: use any and int: for example:

password_contains_digits = int(any(c.isdigit() for c in s))

or use set:

password_set = set(s)
digits = set("1234567890")
password_contains_digits = int(len(password_set & digits) != 0)

or use RegEx:

password_contains_digits = len(re.findall(r'[0-9]', s)) != 0

Also, regex can help you to find repeating symbols :)

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

You could use this code for checking whether it has an uppercase/lowercase:

if s.lower() == s:
    # doesn't have any uppercase
if s.upper() == s:
    # doesn't have any lowercase

This way, you don't have to iterate through the entire string.

\$\endgroup\$

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.