当前位置:  开发笔记 > 编程语言 > 正文

如何检查列表是否只有一个真值?

如何解决《如何检查列表是否只有一个真值?》经验,为你挑选了6个好方法。

在python中,我有一个列表应该只有一个 truthy值(即,bool(value) is True).有没有一种聪明的方法来检查这个?现在,我只是遍历列表并手动检查:

def only1(l)
    true_found = False
    for v in l:
        if v and not true_found:
            true_found=True
        elif v and true_found:
             return False #"Too Many Trues"
    return true_found

这似乎不优雅,不是非常pythonic.有更聪明的方法吗?



1> Jon Clements..:

一个不需要进口:

def single_true(iterable):
    i = iter(iterable)
    return any(i) and not any(i)

或者,可能是更易读的版本:

def single_true(iterable):
    iterator = iter(iterable)
    has_true = any(iterator) # consume from "i" until first true or it's exhuasted
    has_another_true = any(iterator) # carry on consuming until another true value / exhausted
    return has_true and not has_another_true # True if exactly one true found

这个:

看起来确保i有任何真正的价值

继续从迭代中的那一点看,以确保没有其他真正的价值


@MatthewScouten no ...我们在这里使用迭代消耗...尝试运行代码...
任何认为这不是一个可读解决方案的人都应该考虑这个问题:它简洁明了,只依赖于Python的已知行为和常见结构.仅仅因为一个菜鸟不会理解它,不会让它变得可读.它也是一种很好的教学方法,因为它可以立即引起那些看不出它如何运作的人的好奇心.
@wim它不是*any()`的实现细节 - 它是函数的文档特性,以及符合Python规范的任何实现的保证功能.
@MatthewScouten按照可迭代的消费量.一旦找到非假值,`any`将按照文档返回True.在那之后,我们再次寻找一个真正的价值,并且如果发现它被视为失败...那么这将适用于空列表,列表/其他序列,以及任何可迭代的......
@MathewScouten副作用破坏了所有定理!如果`x`是参考透明的,那么`x而不是x = False`才是正确的.
而且,相关性稍差,*无视*不是一个字.无论如何,+1,优雅而简洁的答案.
也许像`seen_one = any(i); seen_another = any(i); return seen_one and not seen_another`.虽然它可能以牺牲禅宗为代价来修复禅#2 ......叹息:)
它完全有道理.`any()`被保证停止消耗第二个命中'True`值,并且迭代器将永远被消耗掉.这是在这里做到这一点的最佳方式.它高效,简短,易读.
"禅的蟒蛇#17:如果实施很难解释,这是一个坏主意.[最后一次Jon Clements发布了相同的代码行](http://stackoverflow.com/a/16522290/674039)那里是一篇很满意的评论,比如"我必须阅读它至少4次,直到我理解它","我不明白它.它的内容如下:如果是真的而不是真的.请帮助我理解." 所有来自不同用户的"即使解释我也无法理解行为".
@Lattyware IMO它不可读,因为它依赖于隐藏的'any`实现细节的先验知识.有python经验的人知道它是短路的并且在无限序列上工作,并且以明显的方式消耗迭代器.但是[错误地]假设它表现得像集合论或数学抽象是很自然的,例如并行地处理有限集合等无序集合,而不是迭代和消费.正是由于这个原因,这条线看起来很尴尬和混乱,正如评论中所证明的那样.
我认为这不难解释,它只是不是非常可读.我同意将它分成几行,如果你想要超清楚,可能还有一个内联注释,对于代码的读者来说可能是值得的,以确保他们理解.然而,鉴于它是一个具有适当名称的函数,我不认为它太难以看到正在发生的事情,而现实是任何非平凡的代码都不会立即被乍一看.
我已经编辑了代码以稍微分开它 - 带有一些额外的注释和显式变量名
Re:这里关于可读性的所有讨论; 我猜想很多人发现这个答案难以理解的原因是*他们不理解`any`的行为,但是他们没有意识到`iter()`创建了一个可消耗的迭代器,或者完全不熟悉耗材迭代器的概念.检查列表是否具有单个"True"值的问题是一个完整的新手程序员可能会遇到的问题; 可以理解的是,这些人发现需要明确地考虑可消耗的迭代器是奇怪和困难的.
@pnuts我投了-1,因为我觉得答案在初始形式上有一些问题.我留下了一条评论,解释了我觉得这是一个缺点,经过一些讨论后,答案随后由作者编辑.这个编辑已经充分改善了答案,所以我收回了我的downvote.你能否确切地解释这与所希望的SO精神的相反之处,或者这个**所希望的精神**对你意味着什么?
-1与[here](http://stackoverflow.com/a/16522290/674039)相同的原因 - 这一行是一个心理障碍.可读性很重要.
我不认为解决方案是如此丑陋 - 我认为将它拆分为非常清晰可读,同时保留了一个好的方法.
我删除了我的downvote,因为替代方案看起来不错.内联注释有点矫枉过正,它显然足以成为自我评论代码._这是第一个需要评论的版本.
如果我能接受2,我也会接受这个.它具有简短,高效和聪明的优点.它还激发了有趣和有用的对话.问题是,对于随意的读者来说,这看起来非常错误.
@joneshf不是.它经常被使用,但它从来都不是一个字.如果您查一查,[大多数词典将其列为"非标准"或"不正确"](http://en.wikipedia.org/wiki/Irregardless).正确的单词将是*无论*或*无关*.

2> David Robins..:

这取决于您是只是在寻找值True还是正在寻找其他可以在True逻辑上评估的值(例如11"hello").如果是前者:

def only1(l):
    return l.count(True) == 1

如果是后者:

def only1(l):
    return sum(bool(e) for e in l) == 1

因为这样可以在一次迭代中完成计数和转换,而无需构建新的列表.


只是指出OP,当发现多个"真"值时,这可能不会短路,因此他们的代码在某些情况下可能会提高效率.
在Python 3中:`list(map(bool,l)).count(True)`
第二个函数可以写成`return sum(对于e in l的bool(e))== 1`.对于算术,`bool`子类`int`和True/False表现为1/0.

3> moooeeeep..:

最详细的解决方案并不总是最不优雅的解决方案.因此,我只添加了一个小修改(为了保存一些冗余的布尔值评估):

def only1(l):
    true_found = False
    for v in l:
        if v:
            # a True was found!
            if true_found:
                # found too many True's
                return False 
            else:
                # found the first True
                true_found = True
    # found zero or one True value
    return true_found

以下是一些比较时间:

# file: test.py
from itertools import ifilter, islice

def OP(l):
    true_found = False
    for v in l:
        if v and not true_found:
            true_found=True
        elif v and true_found:
             return False #"Too Many Trues"
    return true_found

def DavidRobinson(l):
    return l.count(True) == 1

def FJ(l):
    return len(list(islice(ifilter(None, l), 2))) == 1

def JonClements(iterable):
    i = iter(iterable)
    return any(i) and not any(i)

def moooeeeep(l):
    true_found = False
    for v in l:
        if v:
            if true_found:
                # found too many True's
                return False 
            else:
                # found the first True
                true_found = True
    # found zero or one True value
    return true_found

我的输出:

$ python -mtimeit -s 'import test; l=[True]*100000' 'test.OP(l)' 
1000000 loops, best of 3: 0.523 usec per loop
$ python -mtimeit -s 'import test; l=[True]*100000' 'test.DavidRobinson(l)' 
1000 loops, best of 3: 516 usec per loop
$ python -mtimeit -s 'import test; l=[True]*100000' 'test.FJ(l)' 
100000 loops, best of 3: 2.31 usec per loop
$ python -mtimeit -s 'import test; l=[True]*100000' 'test.JonClements(l)' 
1000000 loops, best of 3: 0.446 usec per loop
$ python -mtimeit -s 'import test; l=[True]*100000' 'test.moooeeeep(l)' 
1000000 loops, best of 3: 0.449 usec per loop

可以看出,OP解决方案明显优于此处发布的大多数其他解决方案.正如预期的那样,最好的是具有短路行为的那些,尤其是Jon Clements发布的解决方案.至少对于True长列表中的两个早期值的情况.

这里没有任何True价值相同:

$ python -mtimeit -s 'import test; l=[False]*100000' 'test.OP(l)' 
100 loops, best of 3: 4.26 msec per loop
$ python -mtimeit -s 'import test; l=[False]*100000' 'test.DavidRobinson(l)' 
100 loops, best of 3: 2.09 msec per loop
$ python -mtimeit -s 'import test; l=[False]*100000' 'test.FJ(l)' 
1000 loops, best of 3: 725 usec per loop
$ python -mtimeit -s 'import test; l=[False]*100000' 'test.JonClements(l)' 
1000 loops, best of 3: 617 usec per loop
$ python -mtimeit -s 'import test; l=[False]*100000' 'test.moooeeeep(l)' 
100 loops, best of 3: 1.85 msec per loop

我没有检查统计显着性,但有趣的是,这次FJ建议的方法,尤其是Jon Clements的方法再次显然更优越.


嗯 - 看看早期的真实时间 - 最快不是"0.446"吗?
@JonClements这就是为什么我写了_most_,现在让它更清晰了.(大多数发布,而不是大多数测试......)
@MarkAmery我添加了关于可读性和优雅性的一章(诚然是一小段)和性能评估。我认为,由于这个问题要求聪明,所以两个方面都应加以考虑。如我所见,我提供了解决这两个相关方面的答案。如果您认为此答案没有用,请随时投票。

4> Andrew Clark..:

保留短路行为的单线答案:

from itertools import ifilter, islice

def only1(l):
    return len(list(islice(ifilter(None, l), 2))) == 1

对于具有相对较早的两个或更多真值的非常大的迭代,这将明显快于其他替代方案.

ifilter(None, itr)给出一个只能产生真实元素的迭代(x如果bool(x)返回则是真实的True). islice(itr, 2)给出一个只能产生前两个元素的迭代itr.通过将其转换为列表并检查长度是否等于1,我们可以验证确实存在一个真实元素,而不需要在找到两个之后检查任何其他元素.

以下是一些时序比较:

设置代码:

In [1]: from itertools import islice, ifilter

In [2]: def fj(l): return len(list(islice(ifilter(None, l), 2))) == 1

In [3]: def david(l): return sum(bool(e) for e in l) == 1

表现出短路行为:

In [4]: l = range(1000000)

In [5]: %timeit fj(l)
1000000 loops, best of 3: 1.77 us per loop

In [6]: %timeit david(l)
1 loops, best of 3: 194 ms per loop

没有发生短路的大型清单:

In [7]: l = [0] * 1000000

In [8]: %timeit fj(l)
100 loops, best of 3: 10.2 ms per loop

In [9]: %timeit david(l)
1 loops, best of 3: 189 ms per loop

小清单:

In [10]: l = [0]

In [11]: %timeit fj(l)
1000000 loops, best of 3: 1.77 us per loop

In [12]: %timeit david(l)
1000000 loops, best of 3: 990 ns per loop

因此,sum()对于非常小的列表,该方法更快,但随着输入列表变大,即使无法进行短路,我的版本也会更快.当在大输入端上进行短路时,性能差异很明显.


哎哟.只要了解其他选项,我就三次.如果短路很重要,我会采用OP的代码,因为它更加明显且大致同样有效.

5> Antti Haapal..:

我想获得死灵法师徽章,所以我概括了Jon Clements的优秀答案,保留了短路逻辑和快速谓词检查的好处.

这样就是:

N(真实)= n

def n_trues(iterable, n=1):
    i = iter(iterable)
    return all(any(i) for j in range(n)) and not any(i)

N(真实)<= n:

def up_to_n_trues(iterable, n=1):
    i = iter(iterable)
    all(any(i) for j in range(n))
    return not any(i)

N(真实)> = n:

def at_least_n_trues(iterable, n=1):
    i = iter(iterable)
    return all(any(i) for j in range(n))

m <= N(真实)<= n

def m_to_n_trues(iterable, m=1, n=1):
    i = iter(iterable)
    assert m <= n
    return at_least_n_trues(i, m) and up_to_n_trues(i, n - m)



6> 小智..:
>>> l = [0, 0, 1, 0, 0]
>>> has_one_true = len([ d for d in l if d ]) == 1
>>> has_one_true
True


为什么这会被贬低?我认为这是最简单,最易读的.
推荐阅读
有风吹过best
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有