如何缩短此布尔表达式?

时间:2018-08-16 05:21:21

标签: python python-3.x

我是制作密码生成器的初学者,需要确保密码同时包含数字和大写字母。此while循环的条件是多余的。 for char in password出现两次。你会怎么写?

while not (any(char.isdigit() for char in password) and (any(char.isupper() for 
char in password))):

在循环中,它将生成另一个密码。

我在这里的目标是更好地理解如何构造while循环的表达式,而不是以其他方式解决问题。

5 个答案:

答案 0 :(得分:22)

首先,我希望网站停止使用无用的密码要求。它们减少了密码的熵,使人们更难以记住。如果未在UI中明确列出要求,那么特别糟糕的是,人们可以设计适当的密码而不必猜测您可能为他们设置了什么陷阱。

也就是说,您的语法比某些regex实现要短很多。如果您想应用De Morgan的定律将问题分解为逻辑,那么可能会更容易推理出问题,您可以执行以下操作(在短路性能方面会有所损失)。

while all(not char.isdigit() for char in password)
       or all(not char.isupper() for char in password):

这似乎是您的真正问题,尽管两次通过password。有趣的是,正则表达式方法也存在相同的问题,隐藏在某些其他语法后面。如果您愿意为了通用性,短路能力以及单次通过数据而牺牲解决方案的简洁性,则可以将条件提取为自己的方法,如下所示:

def satisfies(password, *conditions):
    flags = [False] * len(conditions)
    for c in password:
        for i, cond in enumerate(conditions):
            if cond(c):
                flags[i] = True
                if all(flags):
                    return True
    return False

while satisfies(password, str.isdigit, str.isupper):
    pass

逐步执行此操作,它将遍历每个字符和每个条件(例如,需要数字的条件),并检查是否满足要求。如果是这样,它将记录该事件并检查是否可以提前退出。最后,退出for循环的唯一可能方法是,如果password中的任何地方都没有满足条件,那么我们返回False

只是为了好玩,您可以使用reduce()函数获得类似的效果(无需提前停止)。它内置在Python 2.x中,您需要在Python 3.x中从functools导入它。

while not all(reduce(
        lambda (a, b), (d, e): (a or d, b or e),
        ((c.isdigit(), c.isupper()) for c in password))):

这样可以有效地保持您当时是否满足密码中的isdigit和isupper要求的统计信息。检查完整个密码后,您只需使用all()来读取您的记录,并确保您确实满足这两个要求。

如果您的目标是运行时,而不是诸如“传递数据”之类的空想(不要贬低,它们在其他情况下可能会起很大作用),那么您最好的改进将来自某种高性能numpy之类的库旨在将您执行的查询向量化。由于此处要执行的大部分工作不是传递数据,而是对每次传递中的字符进行检查,因此消除传递数据对于运行时间不会有多大作用。通过尽快进行实际检查,您将实现最大的节省。

答案 1 :(得分:4)

我同意提出的答案,但要解决您的原始问题和下一步:

  

@moooeeeep:您是否忘记检查任何(char.islower()中的char   密码)?

     

@Matt Davis:是的,我打算添加下一个,但随后   表情真的很长。您现在看到我的问题了:)

稍微简化您的原始逻辑,我们可以执行以下操作:

while not all([any(map(str.isdigit, password)), any(map(str.isupper, password)), any(map(str.islower, password))]):

即将all()应用于any()的列表。对于测试用例:

["goat", "Goat", "goat1", "GOAT", "GOAT1", "Goat1"]

只有"Goat1"符合所有三个条件,因此可以通过。

答案 2 :(得分:2)

撇开密码的优劣标准有什么问题(我相信我们都必须实施我们当时不同意的规范),没有错与您编写此逻辑表达式的方式相同。

很清楚代码的意思,并且除非您发现它实际上在实践中太慢,否则您不应为获得性能或效率的某些理论上的好处而牺牲清晰度,也不要插队短几个字符。

别忘了您可以使用缩进在Python中的多行代码之间打断,因此您可以添加更多条件而不会失去可读性:

while not (
        any(char.isdigit() for char in password) and
        any(char.isupper() for char in password) and
        any(char.islower() for char in password) and
        any(char.somethingelse() for char in password) ):
    do-something

作为一个初学者,学会编写清晰易懂的代码比担心到处散布几微秒或字节要重要得多。

答案 3 :(得分:1)

类似于lambda版本。返回一组与任何这些测试匹配的元组。从结果中删除(False,False)。长度设置应为2。

input_exp = [ 
    ("abc", False),
    ("A3", True),
    ("A", False),
    ("ab3", False),
    ("3", False),
    ("3A", True),

]

def check(string_):
    """each iteration evaluates to any one of (True,False) /(False,True)/ (False,False).  
    remove False,False from set.  
    check len==2"""

    res = len(set([(c.isupper(), c.isdigit()) for c in string_]) - set([(False, False)])) == 2

    return res


for string_, exp in input_exp:
    got = check(string_)

    if exp is got:
        print("good.  %s => %s" % (string_, got))
    else:
        print("bad .  %s => %s" % (string_, got))

输出:

good.  abc => False
good.  A3 => True
good.  A => False
good.  ab3 => False
good.  3 => False
good.  3A => True

答案 4 :(得分:-1)

使用re模块执行您想要的操作。

import re

pattern = re.compile('[A-Z0-9]+')

password1 = 'asdf1234'
password2 = 'ASDF1234'

for p in (password1, password2):
    if pattern.fullmatch(p):
        print('Password', p, 'is fine')
    else:
        print('Password', p, 'is bad')