【问题标题】:Python: if more than one of three things is true, return falsePython:如果三件事中的一个以上为真,则返回假
【发布时间】:2011-07-14 01:37:40
【问题描述】:

我正在编写一个 django 模型,它允许我的网站拥有优惠券。

优惠券可以有三种类型:终身帐户优惠券、一定期限的优惠券、一定数量的美元优惠券。

为简单起见,我只允许优惠券具有三个可能的值之一(即优惠券不能是 10 美元和 5 个月)。但我想检查何时保存优惠券以确保此规则为真。

目前我有:

true_count = 0
if self.months:
    true_count += 1
if self.dollars:
    true_count += 1
if self.lifetime:
    true_count += 1    

if true_count > 1:
    raise ValueError("Coupon can be valid for only one of: months, lifetime, or dollars")  

我知道有更好的方法可以做到这一点,但我没有看到(称之为 coder's block)。

非常感谢您的帮助。

如果它很重要,这三种类型是 int、int 和 bool

months = models.IntegerField(default=0)
cents = models.IntegerField(default=0)
#dollars = models.FloatField(default=0.00)
#dollars replaced with integer cents per advice of group
lifetime = models.BooleanField(default=False)

【问题讨论】:

  • 问了 9 个深思熟虑的答案后 1 小时,这就是我喜欢 Stackoverflow 的原因

标签: python django


【解决方案1】:

我在类似情况下做过的一件事是:

coupon_types = (self.months, self.dollars, self.lifetime,)

true_count =  sum(1 for ct in coupon_types if ct)
if true_count > 1:
    raise ValueError("Coupon can be valid for only one of: months, lifetime, or dollars")  

现在可以更轻松地添加新的优惠券类型以供将来检查!

【讨论】:

    【解决方案2】:

    您还可以使用列表组合来过滤错误值:

    if len([x for x in [self.months, self.dollars, self.lifetime] if x]) > 1:
        raise ValueError()
    

    或建立 MRAB's answer:

    if sum(map(bool, [self.months, self.dollars, self.lifetime])) > 1:
        raise ValueErrro()
    

    【讨论】:

    • 谢谢。这个解决方案让我轻笑了一下——如果没有包含列表理解的解决方案,它就不是一个 python 问题。
    • sum(bool(x) for x in (self.months, self.dollars, self.lifetime))
    【解决方案3】:

    将数量保存在单个字段中,并将类型设置为使用 choices 的单独字段。

    【讨论】:

    • 发布后我正在考虑这个可能的解决方案。我想知道您提出的方法或当前方法是否具有更好的容错能力。
    • 我认为这里的主要问题是“数量”字段需要存储不同的类型。一个浮点数可以存储所有这些,但你真的想在浮点字段中存储一个布尔值吗?但是,我赞成另一个明确列出类型的字段,因此我 +1。
    • 无论如何都不应该将钱存储在浮点数中。只需将其设为整数美分即可。
    • 将钱存储为整数美分与浮点数的基本原理是什么?在某些应用程序(不是这个)中,需要跟踪部分美分。
    • 没关系 - 快速访问谷歌找到答案stackoverflow.com/questions/3730019/…
    【解决方案4】:

    您的代码看起来不错。原因如下:

    1.) 你写的,你是描述逻辑的人。您可以使用各种语法技巧来减少代码行数(true_count += 1 if self.months else 0,巨大的 if 语句等),但我认为您拥有它的方式是完美的,因为这是您首先要做的试图描述逻辑时想到的。

    把可爱的代码留给编程挑战吧,这就是现实世界。

    2.) 如果您决定需要添加另一种优惠券价值类型,那么您确切地知道您需要做什么:添加另一个 if 语句。在一个复杂的 if 语句中,您最终会面临更艰巨的任务。

    【讨论】:

    • 我不同意这种观点,我认为 OP 希望改进他的代码是件好事。一旦你有多个这样的 if 语句,它就会开始看起来很混乱。一个更优雅的解决方案是可能的,而不会失去清晰度。发布了一些其他解决方案(包括我的),这些解决方案非常清晰,将来更容易扩展。
    • 我也不得不反对。虽然有可能变得过于聪明,但the checked answer非常清晰易懂,而且易于扩展。
    【解决方案5】:
    if (self.months && (self.dollars || self.lifetime))  || (self.dollars && (self.months || self.lifetime)) || (self.lifetime && (self.dollars || self.months))
        raise ValueError("Coupon can be valid for only one of: months, lifetime, or dollars") 
    

    编辑:

    我使用卡诺图 (http://en.wikipedia.org/wiki/Karnaugh_map) 做了一个快速的电路最小化。最终这是具有布尔逻辑的最小可能函数:

    if((self.months && self.dollars) || (self.dollars && self.lifetime) || (self.lifetime && self.months))
        raise ValueError("Coupon can be valid for only one of: months, lifetime, or dollars") 
    

    从逻辑上讲,我的两个陈述都是等价的,但第二个在技术上更快/更有效。

    编辑#2:如果有人对这里感兴趣,这里是 K-Map

    A | B | C | f(A, B, C)
    ----------------------
    0 | 0 | 0 |     0
    ----------------------
    0 | 0 | 1 |     0
    ----------------------
    0 | 1 | 0 |     0
    ----------------------
    0 | 1 | 1 |     1
    ----------------------
    1 | 0 | 0 |     0
    ----------------------
    1 | 0 | 1 |     1
    ----------------------
    1 | 1 | 0 |     1
    ----------------------
    1 | 1 | 1 |     1
    

    减少到:

       C\AB
         -----------------
         | 0 | 0 | 1 | 0 |     
         -----------------      OR      AB + BC + AC
         | 0 | 1 | 1 | 1 |
         -----------------
    

    【讨论】:

    • 虽然代码行数较少,但它使代码更难理解。我将通过更少的代码行来提高可读性。
    • 查看我编辑的答案。它更容易阅读,也是我能想到的最有效的算法。
    • 当潜在值的数量达到四个时,这将如何扩展? IE。如果业务需求发生变化,代金券也可以适用于整数个小马?
    • 变为 6 个ANDs,由逻辑ORs 分隔。它可以像任何具有多个输入的逻辑电路一样扩展
    • 我喜欢 K-Map 的想法;我认为我对它的实现(如下)更具可读性,因为它的功能很随意,而且可以更好地扩展。
    【解决方案6】:

    我认为将其分散在几行上是可以的 - 如果将来有更多属性要测试,这将更容易维护。使用lensum 感觉有点太混淆了

    # Ensure that only one of these values is set
    true_count = 0
    true_count += bool(self.months)
    true_count += bool(self.dollars)
    true_count += bool(self.lifetime)
    

    【讨论】:

      【解决方案7】:

      我不知道这是否更适合你,但这样做会奏效:

      if (self.months && self.dollars) || (self.months && self.lifetime) || (self.dollars && self.lifetime):
         raise ValueError("Coupon can be valid for only one of: months, lifetime, or dollars") 
      

      【讨论】:

        【解决方案8】:

        如果你有 Python2.7 或更新版本

        from collections import Counter
        items_to_test = (self.months, self.dollars, self.lifetime)
        true_count = Counter(map(bool, items_to_test))[True]
        

        【讨论】:

        • 你可以通过 true_count = sum(map(bool,items_to_test)) 使这个 Python 版本不可知
        【解决方案9】:

        比以前更好的解决方案,combinationsanyall。 假设你有所有你想测试的属性在一个名为attributes的序列中:

        from itertools import combinations
        any(map(all, combinations(attributes, 2)))
        

        英文是这样的

        任何长度为 2 的属性组合都为真吗?

        此解决方案适用于任意数量的属性,并且可以修改以测试任意数量的属性是否为真。

        虽然承认它的效率很低,但我会说它非常可爱和可读。

        【讨论】:

        • 这会使许多程序员在手册页上停留半小时或更长时间,所以我不能说它在大多数商店中都可以维护。但我不得不同意,它很可爱。
        • @Ted:哈哈,是的,+1 表示建设性的批评,但我确实认为,如果你很好地评论你的代码,即使是新手程序员也可能看穿如此可爱的 sn-p 背后的逻辑。
        • 您应该将文字 2 替换为 len(attributes)-1,以防将来添加属性
        • @gnibbler:这不是重点......他想知道是否填充了多个属性,而不是除了一个之外的所有属性。
        【解决方案10】:

        怎么样

        if len(filter([self.months, self.dollars, self.lifetime])) > 1:
        ...
        

        我发现它与带有if 子句的列表推导式一样可读,而且更简洁。

        【讨论】:

          【解决方案11】:

          boolint的子类,因为Python最初缺少bool,使用int作为布尔值,所以:

          if self.months + self.dollars + self.lifetime > 1:
              ...
          

          这是因为False == 0True == 1 都是真的。

          【讨论】:

          • 如果 self.months 为 2,则为误报。您需要在求和之前将变量转换为布尔值。
          猜你喜欢
          • 1970-01-01
          • 2013-06-14
          • 2017-12-11
          • 1970-01-01
          • 2017-04-01
          • 1970-01-01
          • 2021-10-15
          • 2016-05-03
          • 2019-03-10
          相关资源
          最近更新 更多