【问题标题】:Count the number of times multiple substrings appear in a string at once计算多个子字符串同时出现在字符串中的次数
【发布时间】:2019-02-18 10:29:38
【问题描述】:

我正在用 Python 编写一个简单的脚本,用于评估评分系统中密码的强度,该评分系统根据密码是否包含学校的大写或小写字母、数字和符号来给予和计分。

其中一个要求是它检查英国 QWERTY 键盘上从左到右连续的 3 个字母或数字,每个实例扣 5 分。例如,密码“qwer123”会因“qwe”、“wer”和“123”而损失 15 分。这怎么可能实现?下面是我当前的代码。

def check():
  user_password_score=0
  password_capitals=False
  password_lowers=False
  password_numbers=False
  password_symbols=False
  password_explanation_check=False
  ascii_codes=[]
  password_explanation=[]
  print("The only characters allowed in the passwords are upper and lower case letters, numbers and these symbols; !, $, %, ^, &, *, (, ), _, -, = and +.\n")
  user_password=str(input("Enter the password you would like to get checked: "))
  print("")
  if len(user_password)>24 or len(user_password)<8:
    print("That password is not between 8 and 24 characters and so the Password Checker can't evaluate it.")
    menu()
  for i in user_password:
    ascii_code=ord(i)
    #print(ascii_code)
    ascii_codes.append(ascii_code)
  #print(ascii_codes)
  for i in range(len(ascii_codes)):
    if ascii_codes[i]>64 and ascii_codes[i]<90:
      password_capitals=True
    elif ascii_codes[i]>96 and ascii_codes[i]<123:
      password_lowers=True
    elif ascii_codes[i]>47 and ascii_codes[i]<58:
      password_numbers=True
    elif ascii_codes[i] in (33,36,37,94,38,42,40,41,45,95,61,43):
      password_symbols=True
    else:
      print("Your password contains characters that aren't allowed.\n")
      menu()
  if password_capitals==True:
    user_password_score+=5
  if password_lowers==True:
    user_password_score+=5
  if password_numbers==True:
    user_password_score+=5
  if password_symbols==True:
    user_password_score+=5
  if password_capitals==True and password_lowers==True and password_numbers==True and password_symbols==True:
    user_password_score+=10
  if password_numbers==False and password_symbols==False:
    user_password_score-=5
  if password_capitals==False and password_lowers==False and password_symbols==False:
    user_password_score-=5
  if password_capitals==False and password_lowers==False and password_numbers==False:
    user_password_score-=5
  #print(user_password_score)
  if user_password_score>20:
    print("Your password is strong.\n")
  else:
    print("That password is weak.\n")
  #don't forget you still need to add the thing that checks for 'qwe' and other stuff.
  menu()

【问题讨论】:

  • 欢迎来到 Stack Overflow。请阅读How to ask,看看您是否可以使您的代码-sn-p 的基本部分更加简洁。
  • 您也可以在codereview.stackexchange.com 上发布您的代码。如果x 是布尔值,您可以将if x == True 替换为if x,将if x == False 替换为if not x
  • 你也可以写contains_numbers = any(char.isdigit() for char in password)contains_uppercase = any(char.isupper() for char in password)

标签: python python-requests


【解决方案1】:

您可以将禁止的序列存储在一组字符串中,并在每次有人使用该序列时降低分数。

password = "qwert123"
score = 42          # initial score
sequences = {       # all in lowercase because of the `lower()` in the loop
    "qwertyuiopasdfghjklzxcvbnm",
    "azertyuiopqsdfghjklmwxcvbn",
    "abcdefghijklmnopqrstuvwxyz",
    "01234567890"
}
match_length = 3                        # length threshold for the sanction
sequences.update({s[::-1] for s in sequences})      # do we allow reverse ?

for c in range(len(password)-match_length+1):
    for seq in sequences:
        if password[c:c+match_length].lower() in seq:
            score-=5
            print(f"'{password[c:c+match_length]}' => -5 !")
            break   # Don't flag the same letters more than once

print(score) # 22 (42-4*5)

【讨论】:

    【解决方案2】:

    最简单的方法是暴力破解所有可能的序列。

    创建 4 个字符串:"1234567890""qwertyuiop""asdfghjkl""zxcvbnm",并使用来自 user_password 的 3 个字符循环遍历每个字符串。

    你可以在check函数的开头初始化这个列表:

    sequences = ["1234567890", "qwertyuiop", "asdfghjkl", "zxcvbnm"]
    

    然后在for i in range(len(ascii_codes))循环内添加:

    if(i<len(ascii_codes)-2):  # since we will be checking for characters up to i+2 in our loop
        flag = False  # initialize a flag to signal finding a match
        for s in sequences:  # loop through each of the 4 keyboard sequences
            if(s.find(user_password[i: i+3].lower()) != -1): 
                user_password_score -= 5
                flag = True
                break 
            if(flag): break
    

    【讨论】:

    • 还要注意循环会在第一次出现后中断
    • @BenoîtPilatte,这就是我们所需要的,任何 3 个字符只会出现一次,并且只会出现在其中一个序列中!
    • @BenoîtPilatte,没错,测试代码,你会发现它确实将分数降低了两次。
    • 好吧,我被你的breaks 和你的循环弄糊涂了。
    【解决方案3】:

    我会创建一个相邻键序列的列表,就像上面提到的其他人一样。然后我会创建一个sliding window function 来生成所有长度为 3 的序列,并将每个序列与密码进行匹配:

    from itertools import islice
    
    keyboard_rows = ['1234567890', 'qwertyuiop', 'asdfghjkl', 'zxcvbnm']
    
    def window(seq, n=3):
        it = iter(seq)
        result = tuple(islice(it, n))
        if len(result) == n:
            yield result
        for elem in it:
            result = result[1:] + (elem,)
            yield result
    
    for row in keyboard_rows:
    
        for seq in window(row, n=3):
            if "".join(seq) in password:
                user_password_score -= 15
    
        # scan other direction <--
        for seq in window(row[::-1], n=3):
            if "".join(seq) in password:
                user_password_score -= 15
    

    【讨论】:

      【解决方案4】:

      如果regular expressions 被允许,你可以在一行中做到这一点:

      import re
      user_password_score = 42
      pwd = 'qwer123'
      user_password_score += (lambda z : -5 * len([match.group(1) for match in re.compile('(?=({0}))'.format('|'.join(["({0})".format(w) for w in [x for y in [[s[i:i+3] for i in range(0,len(s)-2)] for s in ["qwertyuiopasdfghjklzxcvbnm", "azertyuiopqsdfghjklmwxcvbn", "abcdefghijklmnopqrstuvwxyz", "01234567890"]] for x in y]]))).finditer(z) ]))(pwd)
      

      这段代码是等价的:

      import re
      user_password_score = 42
      pwd = 'qwer123'
      seqs = ["qwertyuiopasdfghjklzxcvbnm", "azertyuiopqsdfghjklmwxcvbn", "abcdefghijklmnopqrstuvwxyz", "01234567890"]
      pattern = re.compile('(?=({0}))'.format('|'.join(["({0})".format(w) for w in [x for y in [[s[i:i+3] for i in range(0,len(s)-2)] for s in seqs] for x in y]])))
      penalty = -5 * len([match.group(1) for match in pattern.finditer(pwd) ])
      user_password_score += penalty
      

      以下代码也是等价的(希望也是人类可读的)。我们将逐步打印它,以更好地了解它在做什么。

      import re
      
      def build_pattern(sequences):
          all_triplets = []
          triplets = []
          for seq in sequences:
              for i in range(0, len(seq) - 2):
                  triplets.append(seq[i:i+3])
              all_triplets.append(triplets)
              triplets = []
          expanded_triplets = [ x for y in all_triplets for x in y ]
          print("Plain list of triplets: " + str(expanded_triplets))
          string_pattern = '|'.join( [ "({0})".format(x) for x in expanded_triplets ] )
          lookahead_pattern = '(?=({0}))'.format(string_pattern)
          print("Regex expression: " + lookahead_pattern)
          return re.compile(lookahead_pattern)
      
      password = 'qwer123'
      user_password_score = 42
      print("User password score: " + str(user_password_score))
      sequences = ["qwertyuiopasdfghjklzxcvbnm", 
                   "azertyuiopqsdfghjklmwxcvbn", 
                   "abcdefghijklmnopqrstuvwxyz", 
                   "01234567890"]
      pattern = build_pattern(sequences)
      matches = [ match.group(1) for match in pattern.finditer(password) ]
      print("Matches : " + str(matches))
      matches_count = len(matches)
      penalty = -5 * matches_count
      print("Penalty: " + str(penalty))
      user_password_score += penalty
      print("Final score: " + str(user_password_score))
      

      这是输出:

      User password score: 42
      Plain list of triplets: ['qwe', 'wer', 'ert', 'rty', 'tyu', 'yui', 'uio', 'iop', 'opa', 'pas', 'asd', 'sdf', 'dfg', 'fgh', 'ghj', 'hjk', 'jkl', 'klz', 'lzx', 'zxc', 'xcv', 'cvb', 'vbn', 'bnm', 'aze', 'zer', 'ert', 'rty', 'tyu', 'yui', 'uio', 'iop', 'opq', 'pqs', 'qsd', 'sdf', 'dfg', 'fgh', 'ghj', 'hjk', 'jkl', 'klm', 'lmw', 'mwx', 'wxc', 'xcv', 'cvb', 'vbn', 'abc', 'bcd', 'cde', 'def', 'efg', 'fgh', 'ghi', 'hij', 'ijk', 'jkl', 'klm', 'lmn', 'mno', 'nop', 'opq', 'pqr', 'qrs', 'rst', 'stu', 'tuv', 'uvw', 'vwx', 'wxy', 'xyz', '012', '123', '234', '345', '456', '567', '678', '789', '890']
      Regex expression: (?=((qwe)|(wer)|(ert)|(rty)|(tyu)|(yui)|(uio)|(iop)|(opa)|(pas)|(asd)|(sdf)|(dfg)|(fgh)|(ghj)|(hjk)|(jkl)|(klz)|(lzx)|(zxc)|(xcv)|(cvb)|(vbn)|(bnm)|(aze)|(zer)|(ert)|(rty)|(tyu)|(yui)|(uio)|(iop)|(opq)|(pqs)|(qsd)|(sdf)|(dfg)|(fgh)|(ghj)|(hjk)|(jkl)|(klm)|(lmw)|(mwx)|(wxc)|(xcv)|(cvb)|(vbn)|(abc)|(bcd)|(cde)|(def)|(efg)|(fgh)|(ghi)|(hij)|(ijk)|(jkl)|(klm)|(lmn)|(mno)|(nop)|(opq)|(pqr)|(qrs)|(rst)|(stu)|(tuv)|(uvw)|(vwx)|(wxy)|(xyz)|(012)|(123)|(234)|(345)|(456)|(567)|(678)|(789)|(890)))
      Matches : ['qwe', 'wer', '123']
      Penalty: -15
      Final score: 27
      

      build_pattern 函数中,[ x for y in all_triplets for x in y ] 是将列表列表扩展为普通列表的技巧。在finditer() 中使用像(lmw)|(mwx)|(wxc) 这样的正则表达式模式告诉我们要查找lmw、mwx 和wxc 的所有匹配项。当我们将此模式包装在前瞻 ((?=())) 中时,我们告诉re 它还应该在结果中包含重叠匹配。

      【讨论】:

        猜你喜欢
        • 2020-02-21
        • 2012-02-12
        • 2019-06-03
        • 1970-01-01
        • 1970-01-01
        • 2014-04-24
        相关资源
        最近更新 更多