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

Mat*_*ten 76 python

在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
Run Code Online (Sandbox Code Playgroud)

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

Jon*_*nts 234

一个不需要进口:

def single_true(iterable):
    i = iter(iterable)
    return any(i) and not any(i)
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

这个:

  • 看起来确保i有任何真正的价值
  • 继续从迭代中的那一点看,以确保没有其他真正的价值

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

Dav*_*son 46

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

def only1(l):
    return l.count(True) == 1
Run Code Online (Sandbox Code Playgroud)

如果是后者:

def only1(l):
    return sum(bool(e) for e in l) == 1
Run Code Online (Sandbox Code Playgroud)

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

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

moo*_*eep 40

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

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
Run Code Online (Sandbox Code Playgroud)

以下是一些比较时间:

# 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
Run Code Online (Sandbox Code Playgroud)

我的输出:

$ 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
Run Code Online (Sandbox Code Playgroud)

可以看出,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
Run Code Online (Sandbox Code Playgroud)

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

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

And*_*ark 21

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

from itertools import ifilter, islice

def only1(l):
    return len(list(islice(ifilter(None, l), 2))) == 1
Run Code Online (Sandbox Code Playgroud)

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

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
    
    Run Code Online (Sandbox Code Playgroud)
  • 表现出短路行为:

    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
    
    Run Code Online (Sandbox Code Playgroud)
  • 没有发生短路的大型清单:

    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
    
    Run Code Online (Sandbox Code Playgroud)
  • 小清单:

    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
    
    Run Code Online (Sandbox Code Playgroud)

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

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

Ant*_*ala 14

我想获得死灵法师徽章,所以我概括了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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

N(真实)> = n:

def at_least_n_trues(iterable, n=1):
    i = iter(iterable)
    return all(any(i) for j in range(n))
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)


小智 11

>>> l = [0, 0, 1, 0, 0]
>>> has_one_true = len([ d for d in l if d ]) == 1
>>> has_one_true
True
Run Code Online (Sandbox Code Playgroud)

  • 为什么这会被贬低?我认为这是最简单,最易读的. (4认同)

Jor*_*ley 5

if sum([bool(x) for x in list]) == 1
Run Code Online (Sandbox Code Playgroud)

(假设您的所有值都是布尔值。)

这可能会更快,只是总结一下

sum(list) == 1   
Run Code Online (Sandbox Code Playgroud)

尽管它可能会导致一些问题,具体取决于列表中的数据类型。


kar*_*ikr 5

你可以做:

x = [bool(i) for i in x]
return x.count(True) == 1
Run Code Online (Sandbox Code Playgroud)

或者

x = map(bool, x)
return x.count(True) == 1
Run Code Online (Sandbox Code Playgroud)

基于@JoranBeasley 的方法:

sum(map(bool, x)) == 1
Run Code Online (Sandbox Code Playgroud)