I am trying to build a strong password checker using Python. The conditions of the password are as follows:
It has at least 6 characters and at most 20 characters.
It must contain at least one lowercase letter, at least one uppercase letter,
and at least one digit.
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 strongPasswordChecker(s), that takes a string s as input, and return the MINIMUM change required to make s a strong password. If s is already strong, return 0.
Insertion, deletion or replace of any one character are all considered as one change.
The following is my attempt:
import re
class Solution:
def strongPasswordChecker(self, s: str) -> int:
# Holds the change
change = 0
# Checks if the password length is less than 6
if len(s) < 6:
change += 6 - len(s)
# Checks if the password length is greater than 20
elif len(s) > 20:
change += len(s) - 20
# Checks if the password has at least one digit
elif re.search(r'\d', s):
change += 1
# Checks if the password has at least one upper case letter
elif re.search(r'[A-Z]', s):
change += 1
# Checks if the password has at least one lower case letter
elif re.search(r'[a-z]', password):
change += 1
# Checks for repeating characters
for i in range(1, len(s)):
if i >= 3 and i < len(s):
if s[i] == s[i + 1] and s[i + 1] == s[i + 2]:
change += 1
return change
Despite checking for the repeating characters with the if statement above, I'm still getting the following error:
IndexError: String Index out of range
The problem is this statement can go out of bounds potentially, for example when i == len(s) - 1 then s[i + 1] and s[i + 2] will both index out of bounds.
for i in range(1, len(s)):
if i >= 3 and i < len(s):
if s[i] == s[i + 1] and s[i + 1] == s[i + 2]:
change += 1
If you want to make sure you don't have groups of 3 or longer, I'd use itertools.groupby
>>> any(len(list(g)) > 2 for k, g in groupby('aabbcc'))
False
>>> any(len(list(g)) > 2 for k, g in groupby('aabbbbbcc'))
True
To replace your for loop in your code, you'd use this like
elif any(len(list(g)) > 2 for k, g in groupby(s)):
change += 1
Related
I'm generating a random password with a desired length. I want it to have at least 2 uppercase letters, 2 lowercase letters, 2 digits and 2 special characters. I've tried multiple things, but every time I get this recursion depth error.
Can anybody tell me what I've done wrong?
list_lower =['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']
list_upper = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N', 'O','P','Q','R','S','T','U','V','W','X','Y','Z']
list_digit = [1,2,3,4,5,6,7,8,9,0]
def generatePassword(desiredLength: int) -> str:
x = 0
password = ""
for x in range (desiredLength):
password = password + chr(random.randint(33,126))
list(password)
list_password = list(password)
times_lower = 0
times_upper = 0
times_digit = 0
times_special = 0
for character in list_password:
if character in list_lower:
times_lower += 1
elif character in list_upper:
times_upper += 1
elif character in list_digit:
times_digit += 1
else:
times_special +=1
if times_lower >= 2 and times_upper >= 2 and times_digit >= 2 and times_special >= 2:
return password
else:
return generatePassword(desiredLength)
generatePassword(7)
I get the error in line 30 which makes the function recursive.
Calling generatePassword(7) will never generate a password with 2 of each of 4 distinct categories.
You don't need recursion at all.
def generatePassword(desiredLength: int) -> str:
while True:
password = ""
for x in range (desiredLength):
password = password + chr(random.randint(33,126))
times_lower = 0
times_upper = 0
times_digit = 0
times_special = 0
for character in password:
if character in list_lower:
times_lower += 1
elif character in list_upper:
times_upper += 1
elif character in list_digit:
times_digit += 1
else:
times_special +=1
if times_lower >= 2 and times_upper >= 2 and times_digit >= 2 and times_special >= 2:
return password
else
print ("Rejecting ", password)
That will loop forever if asked to generate a password of length 7 or less. We can improve that by checking the desired length first
if desiredLength < 8:
raise ArgumentError("Cannot generate passwords shorter than 8 characters")
times_digit will never be >= 2 because it tests stings (e.g. "2" against the integers in your list, (e.g. 2) change your list_digit to
list_digit = ["1","2","3","4","5","6","7","8","9","0"]
and try again.
By the way this could be done much simpler and doensn't need a recursive function.
If you are generating passwords, it's important that you generate ones that actually have enough randomness to not be predictable.
Random string generation with upper case letters and digits in Python
Has a great breakdown of how to generate a password that's truly random:
''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N))
(adding "special characters" and "lowercase characters" omitted to preserve the existing code)
I know that this is a somewhat oblique answer (i.e. it does not answer the question directly), so here's a potential solution if you still need the "it must contain these types of characters" (even though that would actually reduce security):
import random
import string
from collections import Counter
def gen(N):
return ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(N))
while True:
pw = gen(8)
counts = Counter(pw)
upper = lower = digit = special = 0
for (letter, count) in counts.items():
if (letter in string.ascii_lowercase):
lower += 1
elif (letter in string.ascii_uppercase):
upper += 1
elif (letter in string.digits):
digit += 1
else:
special += 1
pass
if (lower > 1 and upper > 1 and digit > 1 and special > 1):
print("password is {}".format(pw))
break
print("failed password: {}".format(pw))
I am trying to count the longest length of string in alphabetical order
s = 'abcv'
longest = 1
current = 1
for i in range (len(s) - 1):
if s[i] <= s[i+1]:
current += 1
else:
if current > longest:
longest = current
current = 0
i += 1
print longest
For this specific string, 'Current' ends up at the correct length, 4, but never modifies longest.
EDIT: The following code now runs into an error
s = 'abcv'
current = 1
biggest = 0
for i in range(len(s) - 1):
while s[i] <= s[i+1]:
current += 1
i += 1
if current > biggest:
biggest = current
current = 0
print biggest
It seems my logic is correct , but I run into errors for certain strings. :(
Although code sources are available on the internet which print the longest string, I can't seem to find how to print the longest length.
break will jump behind the loop (to sam indentation as the for statement. continue will jump to start of loop and do the next iteration
Your logic in the else: statement does not work - you need to indent it one less.
if s[i] <= s[i+1]:
checks for "is actual char less or equal then next char" - if this is the case you need to increment your internal counter and set longest if it is longer
You might get into trouble with if s[i] <= s[i+1]: - you are doing it till len(s)-1. "jfjfjf" is len("jfjfjf") = 6 - you would iterate from 0 to 5 - but the if accesses s[5] and s[6] which is more then there are items.
A different approach without going over explicit indexes and split into two responsibilities (get list of alphabetical substring, order them longest first):
# split string into list of substrings that internally are alphabetically ordered (<=)
def getAlphabeticalSplits(s):
result = []
temp = ""
for c in s: # just use all characters in s
# if temp is empty or the last char in it is less/euqal to current char
if temp == "" or temp[-1] <= c:
temp += c # append it to the temp substring
else:
result.append(temp) # else add it to the list of substrings
temp = "" # and clear tem
# done with all chars, return list of substrings
return result
# return the splitted list as copy after sorting reverse by length
def SortAlphSplits(sp, rev = True):
return sorted(sp, key=lambda x: len(x), reverse=rev)
splitter = getAlphabeticalSplits("akdsfabcdemfjklmnopqrjdhsgt")
print(splitter)
sortedSplitter = SortAlphSplits(splitter)
print (sortedSplitter)
print(len(sortedSplitter[0]))
Output:
['ak', 's', 'abcdem', 'jklmnopqr', 'dhs']
['jklmnopqr', 'abcdem', 'dhs', 'ak', 's']
9
This one returns the array of splits + sorts them by length descending. In a critical environment this costs more memory then yours as you only cache some numbers whereas the other approach fills lists and copies it into a sorted one.
To solve your codes index problem change your logic slightly:
Start at the second character and test if the one before is less that this. That way you will ever check this char with the one before
s = 'abcvabcdefga'
current = 0
biggest = 0
for i in range(1,len(s)): # compares the index[1] with [0] , 2 with 1 etc
if s[i] >= s[i-1]: # this char is bigger/equal last char
current += 1
biggest = max(current,biggest)
else:
current = 1
print biggest
You have to edit out the else statement. Because consider the case where the current just exceeds longest, i.e, from current = 3 and longest =3 , current becomes 4 by incrementing itself. Now here , you still want it to go inside the if current > longest statement
s = 'abcv'
longest = 1
current = 1
for i in range (len(s) - 1):
if s[i] <= s[i+1]:
current += 1
#else:
if current > longest:
longest = current
current = 0
i += 1
longest = current
print longest
Use a while condition loop, then you can easy define, at what condition your loop is done.
If you want QualityCode for longterm:
While loop is better practice than a break, because you see the Looping condition at one place. The simple break is often worse to recognize inbetween the loopbody.
At the end of the loop, current is the length of the last substring in ascending order. Assigning it to longest is not right as the last substring in ascending is not necessarily the longest.
So longest=max(current,longest) instead of longest=current after the loop, should solve it for you.
Edit: ^ was for before the edit. You just need to add longest=max(current,longest) after the for loop, for the same reason (the last ascending substring is not considered). Something like this:
s = 'abcv'
longest = 1
current = 1
for i in range (len(s) - 1):
if s[i] <= s[i+1]:
current += 1
else:
if current > longest:
longest = current
current = 0
i += 1
longest=max(current,longest) #extra
print longest
The loop ends when there is no code after the tab space so technically your loop has already ended
Hey everyone I have been struggling on the longest palindrome algorithm challenge in python 2.7. I am getting close but have a small error I can't figure out. I have the palindrome working, but cannot get longest palindrome to print correct, either gives me a character buffer error or prints 0.
def palindrome(string):
string = "".join(str.split(" ")).lower()
i = 0
while i < len(string):
if string[i] != string[(len(string) - 1) - i]:
return False
i += 1
return True
print palindrome("never odd or even")
def longest_palindrome(string):
best_palindrome = 0
i1 = 0
while i1 < len(string):
length = 1
while (i1 + length) <= len(string):
substring = string.split(i1,length)
if palindrome(substring) and (best_palindrome == 0 or len(substring) > len(best_palindrome)):
best_palindrome = substring
length += 1
i1 += 1
return best_palindrome
print longest_palindrome("abcbd")
From what I understand, your first method was to check if a string is a palindrome or not and your second method is to find the longest palindrome.
The palindrome code that you posted always returned true no matter what the input was because
string = "".join(str.split(" ")).lower()
returns an empty string. I changed this part of your code to
string = string.replace(" ", "").lower()
which I believe gives you the desired effect of removing all spaces and making the string into lowercase.
Next, your second method should be looping through all possible substrings of the inputted string and check if a) its a palindrome and b) if it is longer than the previous largest palindrome.
An example for the string "doloa" would be:
doloa; is palindrome=false;
dolo; is palindrome=false;
dol; is palindrome=false;
do; is palindrome=false;
d; is palindrome=true; is bigger than previous large palindrome=true;
oloa; is palindrome=false;
olo; is palindrome=true; is bigger than previous large palindrome=true;
you would continue this loop for the whole string, and in the end, your variable 'best_palindrome' should contain the largest palindrome.
I fixed your code and I believe this should work (please tell me if this is your desired output).
def palindrome(string):
comb = string.replace(" ", "").lower()
# print(comb)
# print(len(comb))
i = 0
while i < len(comb):
# print(comb[i] + ":" + comb[(len(comb) - 1) - i] + " i: " + str(i) + ", opposite: " + str((len(comb) - 1) - i))
if comb[i] != comb[(len(comb) - 1) - i]:
return False
i += 1
return True
print palindrome("never odd or even")
def longest_palindrome(string):
best_palindrome = ""
i1 = 0
while i1 < len(string):
length = 0
while (i1 + length) <= len(string):
substring = string.replace(" ", "").lower()
substring = substring[i1:len(substring)-length]
#print("Substring: " + str(substring))
if palindrome(substring) and (best_palindrome == "" or len(substring) > len(best_palindrome)):
best_palindrome = substring
length += 1
i1 += 1
return best_palindrome
print longest_palindrome("bgologdds")
Note: I change the name of some of the variables and I also added some print strings for debugging. You can delete those or uncomment them for future debugging.
This code finds the longest alphabetical substring in a string (s).
letter = s[0]
best = ''
for n in range(1, len(s)):
if len(letter) > len(best):
best = letter
if s[n] >= s[n-1]:
letter += s[n]
else:
letter = s[n]
It works most of the time, but occasionally it gives wrong answers and I am confused why it only works sometimes. for example when:
s='maezsibmhzxhpprvx'
It said the answer was "hpprv" while it should have been "hpprvx".
In another case, when
s='ysdxvkctcpxidnvaepz'
It gave the answer "cpx", when it should have been "aepz".
Can anyone make sense of why it does this?
You should move this check
if len(letter) > len(best):
best = letter
after the rest of the loop
Your routine was almost ok, here's a little comparison between yours, the fixed one and another possible solution to your problem:
def buggy(s):
letter = s[0]
best = ''
for n in range(1, len(s)):
if len(letter) > len(best):
best = letter
if s[n] >= s[n - 1]:
letter += s[n]
else:
letter = s[n]
return best
def fixed(s):
letter = s[0]
best = ''
for n in range(1, len(s)):
if s[n] >= s[n - 1]:
letter += s[n]
else:
letter = s[n]
if len(letter) > len(best):
best = letter
return best
def alternative(s):
result = ['' for i in range(len(s))]
index = 0
for i in range(len(s)):
if (i == len(s) - 1 and s[i] >= s[i - 1]) or s[i] <= s[i + 1]:
result[index] += s[i]
else:
result[index] += s[i]
index += 1
return max(result, key=len)
for sample in ['maezsibmhzxhpprvx', 'ysdxvkctcpxidnvaepz']:
o1, o2, o3 = buggy(sample), fixed(sample), alternative(sample)
print "buggy={0:10} fixed={1:10} alternative={2:10}".format(o1, o2, o3)
As you can see in your version the order of the inner loop conditional is not good, you should move the first conditional to the end of loop.
The logic is almost okay except that if letter grows on the last loop iteration (when n == len(s) - 1), best is not changed that last time. You may insert another best = letter part after the loop, or re-think carefully the program structure so you won't repeat yourself.
My intent is to have the program list all strings in a text file that have 3 sets of double letters. Here is the function that is supposed to return True if 3 or more double letter sets are found:
def three_double(s):
doubnum = 0
i=0
while i < len(s)-1:
if s[i] == s[i+1]:
doubnum += 1
elif doubnum >= 3:
return True
else:
i += 1
return False
I'm not sure why it doesn't print anything. Here is the rest of the program.
# Function to read and apply the three_double test to each string in
# an input file. It counts the number of results.
def find_three_double(fin):
count = 0
for w in fin:
w = w.strip()
if three_double(w):
print w
count = count + 1
if count == 0:
print '<None found>'
else:
print count, 'found'
# Bring in a package to access files over the web
import urllib
# Access the file containing the valid letters
words_url = "http://thinkpython.com/code/words.txt"
words_file = urllib.urlopen(words_url)
# Apply the actual test
find_three_double(words_file)
I didn't read your code carefully at first, turns out it isn't related to read() or readlines() as you are iterating in find_three_doubles() function.
In your three_double() function:
while i < len(s)-1:
if s[i] == s[i+1]:
doubnum += 1
elif doubnum >= 3:
return True
else:
i += 1
return False
There are two problems:
You need to increment i by 1 otherwise the while loop will never stop if there is a "double".
You also need to change elif to if here because otherwise some qualified words will not be selected.
Fixed Code:
def three_double(s):
doubnum = 0
i=0
while i < len(s)-1:
if s[i] == s[i+1]:
doubnum += 1
if doubnum >= 3:
return True
i += 1
return False
# Function to read and apply the three_double test to each string in
# an input file. It counts the number of results.
def find_three_double(fin):
count = 0
for w in fin:
w = w.strip()
if three_double(w):
print w
count = count + 1
if count == 0:
print '<None found>'
else:
print count, 'found'
# Bring in a package to access files over the web
import urllib
# Access the file containing the valid letters
words_url = "http://thinkpython.com/code/words.txt"
words_file = urllib.urlopen(words_url)
# Apply the actual test
find_three_double(words_file)
Results:
aggressiveness
aggressivenesses
allottee
allottees
appellee
appellees
barrenness
barrennesses
bookkeeper
bookkeepers
bookkeeping
bookkeepings
cheerlessness
cheerlessnesses
committee
committees
greenness
greennesses
heedlessness
heedlessnesses
heelless
hyperaggressiveness
hyperaggressivenesses
keelless
keenness
keennesses
masslessness
masslessnesses
possessiveness
possessivenesses
rottenness
rottennesses
sleeplessness
stubbornness
stubbornnesses
successfully
suddenness
suddennesses
sullenness
sullennesses
toolless
wheelless
whippoorwill
whippoorwills
woodenness
woodennesses
46 found
itertools.groupby can greatly simplify your program (= less bugs)
from itertools import groupby
import urllib
def find_three_double(words_file):
for word in words_file:
word = word.strip()
if sum(sum(1 for i in g) == 2 for k,g in groupby(word)) == 3:
print word
# Access the file containing the valid letters
words_url = "http://thinkpython.com/code/words.txt"
words_file = urllib.urlopen(words_url)
# Apply the actual test
find_three_double(words_file)
Explanation:
inside the generator expression we see groupby(word). This scans the word and gathers the double letters together.
sum(1 for i in g) is applied to each group. It is equivalent to finding the length of the group. If the length is 2, then this is a double letter so sum(1 for i in g) == 2 evaluates to True
The outer sum() adds up all the True and False values, True is added as 1 and False is added as 0. If there are exactly 3 True values, the word is printed
while i < len(s)-1:
if s[i] == s[i+1]:
doubnum += 1
elif doubnum >= 3:
return True
else:
i += 1
If your first check (s[i] == s[i+1]) is True, then you'll never increment i so the loop continues forever.