高效计算真和假的方法

6

这可能是一个微不足道的问题,但我想了解其他更聪明、更有效的解决方法。

我有一个项目列表,每个项目都有一个属性 a,其值为二进制。

  • 如果列表中的每个项目都满足 a == 0,那么我会设置另一个变量 b = 0
  • 如果列表中的每个项目都满足 a == 1,那么我会设置 b = 1
  • 如果列表中混合了 a == 0a == 1,那么我会设置 b = 2

我可以使用一个集合来跟踪 a 值的类型,这样如果在遍历列表后集合中有两个项目,那么我就可以设置 b = 2,而如果集合中只有一个项目,我就取出该项目(0 或 1)并用它来设置 b

还有更好的方法吗?


3
在这里,“更好”是主观的,也有些取决于你预计最常遇到的3种情况中的哪一种,甚至可能取决于列表的长度等因素。 - mgilson
@mgilson,说得好。我不知道哪种情况会比其他情况更常见,属性a是基于用户输入设置的。但我知道列表的长度将很短,即<= 1000项。 - ChrisG
10个回答

26

只需一次遍历列表,不需要构建额外的数据结构:

def zot(bs):
    n, s = len(bs), sum(bs)
    return 1 if n == s else 2 if s else 0

不错。Zot是什么意思?零一二吗? - bcollins
1
这里需要注意一下特殊情况 bs == []。有趣的是我们的解决方案都以相同的方式处理它(zotcategory 都返回 1),但我不确定这是否是正确的响应。all([]) 返回 True,而 any([]) 返回 False,因此如果我按照不同的方式组织我的 if 语句,category([]) 将返回 0 - senderle
我认为问题陈述是自相矛盾的:对于空输入,所有值都为0和1都是显然成立的。因此,我们返回两个正确答案的总和;-) - Tim Peters
1
这太聪明了。这就是核心开发人员日常思考的方式吗?我在一千年内都想不到这种方法。 - temporary_user_name
10
啊,但是下一次你遇到类似的问题时,它将会自然而然地浮现在你脑海中。这样持续几十年,你的技能库就会不断扩大啦;-) - Tim Peters

18

我建议使用anyall。 我认为这样做的好处在于可读性,而不是聪明或效率。 例如:

>>> vals0 = [0, 0, 0, 0, 0]
>>> vals1 = [1, 1, 1, 1, 1]
>>> vals2 = [0, 1, 0, 1, 0]
>>> def category(vals):
...     if all(vals):
...         return 1
...     elif any(vals):
...         return 2
...     else:
...         return 0
... 
>>> category(vals0)
0
>>> category(vals1)
1
>>> category(vals2)
2

如果您愿意,这可以稍微缩短一些:

>>> def category(vals):
...     return 1 if all(vals) else 2 if any(vals) else 0
... 

这适用于任何可以被__nonzero__(或Python 3中的__bool__)解释为具有真或假值的内容。


+1 正是我几分钟前要发布的回答,如果我的ISP没挂掉的话。 :-)(不过除了一行代码打高尔夫之外)。 - Lennart Regebro
不是代码高尔夫,你能看到那个可怕的长参数名吗? - Karoly Horvath

15

有人提到了“代码高尔夫”,所以我忍不住要对@senderle的变体进行改编:

[0,2,1][all(vals) + any(vals)]
< p >< em >简要说明:这使用布尔值的整数等价物来索引所需响应的列表。如果 < code > all 为真,则 < code > any 也必须为真,因此它们的总和为 < code > 2 。 < code > any 单独给出 < code > 1 ,没有匹配项则返回 < code > 0 。 这些索引返回列表中对应的值。

如果可以修改原始要求以使用 < code > any + all 的整数形式,则更加简单,只需返回整数 < code > any + all 。


3

使用字典:

zonk_values = {frozenset([0]): 0, frozenset([1]): 1, frozenset([0, 1]): 2}
def zonk(a):
    return zonk_values[frozenset(a)]

这也只需要对列表进行一次遍历。

没错,它是单遍扫描,但它会访问列表中的每个对象并在执行过程中构建一个'frozenset'。 - martineau

2
你也可以使用集合。
s = set([i.a for i in your_list])
if len(s) == 1:
    b = s.pop()
else:
    b = 2

1
一个集合对象不支持索引。请使用next(iter(s))。另外,为什么要用i.a - Noctua
我只是按照问题中所示,从对象列表中提取a值:"我有一个项目列表,每个项目都有一个属性a,其值为二进制。" - bcollins
是的,感谢Noctua的评论。集合不支持索引。我也看到了一些奇怪的行为,并将其删除了。我认为我最喜欢的答案是any/all。 - bcollins

2
def zot(bs):
    return len(set(bs)) if sum(bs) else 0

1
你可以定义两个布尔变量hasZerohasOne,在迭代列表时如果对应的值被满足,则将它们设置为True。然后,如果hasZero和hasOne都成立,则b = 2,如果只有hasOne成立,则b = 1,如果只有hasZero成立,则b = 0
另一种方法是:可以沿着列表累加所有a的值。如果sumA == len(list),则b = 1,如果sumA == 0,则b = 0,如果0 < sumA < len(list),则b = 2

1

短路解决方案。这可能是在Python中最有效的方式。

编辑:根据评论中的建议,包含了anyall

编辑2:现在它是一个一行代码。

b = 1 if all(A) else 2 if any(A) else 0

1
内置的 any()all() 函数会短路并且很可能比任何显式的遍历项的循环更快。 - martineau
@martineau 在C语言中实现了显式的for循环,就像anyall一样。我不明白为什么迭代会比你的方法慢。 - Shashank
循环在C语言中的实现方式不同。 - martineau
@martineau 好的,我会推迟。我编辑了我的答案来使用 anyall - Shashank
1
所有的CPython都是用C实现的。不同之处在于,在循环中执行for循环涉及解释循环内的字节码,而对all()any()的调用则没有。在我的机器上,手动实现应用于大列表的any()比内置版本慢大约三倍。 - Sven Marnach
通过使用 timeit 模块来验证。 - martineau

0

这与senderle的建议类似,但编写为访问对象的a属性。

from random import randint

class Item(object):
    def __init__(self, a):
        self.a = a

all_zeros = [Item(0) for _ in xrange(10)]
all_ones = [Item(1) for _ in xrange(10)]
mixture = [Item(randint(0, 1)) for _ in xrange(10)]

def check(items):
    if all(item.a for item in items):
        return 1
    if any(item.a for item in items):
        return 2
    else:
        return 0

print 'check(all_zeros):', check(all_zeros)
print 'check(all_ones):', check(all_ones)
print 'check(mixture):', check(mixture)

0

您可以使用列表迭代器

>>> L = [0, 0, 0, 0, 0]
>>> L1 = [1, 1, 1, 1, 1]
>>> L2 = [0, 1, 0, 1, 0]
>>> def fn(i):
...     i = iter(i)
...     if all(i): return 1
...     return 2 if any(i) else 0
... 
>>> fn(L)
0
>>> fn(L1)
1
>>> fn(L2)
2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接