Home > Mobile >  Python regex to match 3 consecutive characters in the alphabet but not necessarily side by side
Python regex to match 3 consecutive characters in the alphabet but not necessarily side by side

Time:04-15

I have to build a python function that uses regex to check the complexity of a password which should have at least 8 characters, contains digits, letters and special symbols and last rules (the hardest) : not contain three consecutive letters or three consecutive digits. To illustrate the last rule : Mz2a%yc98B will not be accepted since it contains 3 consecutives letters : aBc

I did well for the first two rules but I have no clue of what can I do for the last rule. It's pretty complex I think. Some people told me that's impossible to do that using regex but actually it has to be possible or my teacher is crazy ?

Thank you very much !

My code so far :

import re

pattern = re.compile(r"")
while True:
    password = input("Enter your password")
    if re.match(r"(?=.*[a-z])(?=.*[A-Z])(?=(.*[!%@$#&^*()\-__ .]){1,})(?=.*[0-9]).{8,}", password):
        result = pattern.match(password)
        print("Your password is strong enough")
    else:
        print("Your password is not strong enough")

And also in the example password I gave yes they are aBc, the B is the last characters. And it's just an example. When I mean consecutives, I mean consecutive in the alphabet. If for example efg letters are used even spreaded in the password, it must detect it. Thanks

CodePudding user response:

Here is a solution to the third criterion of your problem set.

import string
from more_itertools import windowed

alphabet = string.ascii_lowercase
numbers = string.digits

passwords = ["Mz2a%yc98B", "a1b2g3D!", "Tr0ub4dor?"]

def find_triplets(charset: set, target: str) -> bool:
    if len(charset) < 3:
        return False
    else:
        chars = "".join(sorted(charset))
        triplets = ["".join(tpl) for tpl in windowed(chars, 3)]
        return any(triplet in target for triplet in triplets)

for pwd in passwords:
    # we use sets to get rid of duplicatess
    alpha = set()
    nums = set()
    for char in pwd:
        if char.isalpha():
            alpha.add(char.lower())
        elif char.isdigit():
            nums.add(char)
        else:
            pass # we don't care about symbols here
    if find_triplets(alpha, alphabet) or find_triplets(nums, numbers):
        print(f"Your password {pwd} contains 3 consecutive letters or numbers")
    else:
        print(f"Your password {pwd} is good enough, I guess")

After the setup code, we first define a function to generate 3-character strings (triplets) from the input charset, after it has been sorted and turned into a string itself. This uses the windowed() function from the very helpful 3rd party more_itertools module. The function returns True if it finds any triplets that match a consecutive sequence in the target string, and False if it doesn't. Simple enough.

Next, we iterate over our list of passwords (you used input() in your code, which is fine). First, we iterate over the password string character by character and add them to the appropriate set (lowercased in the case of alphabetic characters). We then call our find_triplets() function to test for consecutive runs of letters or numbers, and print a message accordingly.

You'll obviously want to change some of this to fit into your assignment, but I'll leave that up to you.

There is another method using re.search(), but I think any(triplet in target for triplet in triplets) is a lot more Pythonic.

CodePudding user response:

You ask if it is possible to do that with a regular expression? Certainly! Is it pretty? That's in the eye of the beholder.

You need a regular expression that looks like this (with the case-indifferent flag set):

^(?=.*\d)(?=.*[a-z])(?=.*[<special symbols here>])(?!<no 3 digits that are consecutive>)(?!<no three letters that are consecutive>).{8}

Let's look at the negative lookahead

(?!<no 3 digits that are consecutive>) 

We can write that as follows.

(?!(?:(?=.*0)(?=.*1)(?=.*2))|(?:(?=.*1)(?=.*2)(?=.*3))|(?:(?=.*2)(?=.*3)(?=.*4))|(?:(?=.*3)(?=.*4)(?=.*5))|(?:(?=.*4)(?=.*5)(?=.*6))|(?:(?=.*5)(?=.*6)(?=.*7))|(?:(?=.*6)(?=.*7)(?=.*8))|(?:(?=.*7)(?=.*9)(?=.*9)))

Demo

This regular expression contains the following elements.

(?!          # begin negative lookahead       
  (?:        # begin non-capture group
    (?=.*0)  # match > 0 characters followed by 0 (positive lookahead) 
    (?=.*1)  # match > 0 characters followed by 1 
    (?=.*2)  # match > 0 characters followed by 2
  )          # end non-capture group
  |          # or
  ... similar for (?:(?=.*1)(?=.*2)(?=.*3))
  ...
)            # end negative lookahead

The construction of

(?!<no three letters that are consecutive>)

is similar (containing an alternation with 23 elements, a, b and c to x, y and z).

  • Related