如何检查列表中只有一个真实值?


82

在python中,我有一个列表,该列表应仅包含一个真实值(即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。有更聪明的方法吗?


2
我认为您的解决方案还不错,并且是pythonic!
2013年

1
普通Lisp :(= 1 (count-if #'identity list))
卡兹(Kaz)

7
sum(lst) == 1
帕尔GD

要明确:您是否要检查是否只有一个True或一个真实值?
Marcin 2013年

Answers:


44

最冗长的解决方案并不总是最简单的解决方案。因此,我仅添加了一个较小的修改(以节省一些冗余的布尔值评估):

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提出的方法似乎明显更好。


4
嗯-查看早期的真实时间-不是0.446最快的吗?
乔恩·克莱门茨

2
这就是为什么我写的最多的@JonClements现在变得更清楚了。(大多数已发布,而不是大多数经过测试的...)
moooeeeep

1
我怀疑JonClement的是如此之快,因为大多数的any用C实现
马修Scouten

1
+1作为开场白。所有的答案sum实际上都比OP的简单和直接代码差..
2013年

2
@MarkAmery我添加了有关可读性和美观性的部分(承认这是一个简短的部分)和性能评估。我认为,由于这个问题要求聪明,所以两个方面都应加以考虑。如我所见,我提供了解决这两个相关方面的答案。如果您认为此答案没有用,请随时投票。
moooeeeep

256

不需要导入的一种:

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

或者,也许是一个更具可读性的版本:

def single_true(iterable):
    iterator = iter(iterable)

    # consume from "i" until first true or it's exhausted
    has_true = any(iterator) 

    # carry on consuming until another true value / exhausted
    has_another_true = any(iterator) 

    # True if exactly one true found
    return has_true and not has_another_true

这个:

  • 看起来确保i具有任何真实价值
  • 不断从迭代中寻找这一点,以确保没有其他真正的价值

34
@MatthewScouten否...我们正在这里迭代中消费...尝试运行代码...
乔恩·克莱门茨

12
@MatthewScouten根据迭代的消耗。any一旦发现非false值,文档将立即返回True。此后,我们再次寻找真实值,如果发现该值,则将其视为失败...因此,它将适用于空列表,列表/其他序列以及任何可迭代的内容...
乔恩·克莱门茨

12
@MathewScouten副作用打破了所有定理!x and not x = False仅在x参照透明的情况下才是正确的。

14
@wim不是实现的详细信息any()-它是函数的文档功能,并且是符合Python规范的任何实现的保证功能。
加雷斯·拉蒂

17
任何认为这不是可读的解决方案的人都应该考虑这一点:简洁并且仅依赖于已知的行为和Python的通用构造。仅仅因为菜鸟不理解它,就不会使其可读。这也是传授已知信息的极好方法,因为它会引起那些不了解其工作原理的人立即产生好奇心。
dansalmo 2013年

49

这取决于您是在寻找该值True还是在寻找其他True逻辑上会求值的值(如11"hello")。如果是前者:

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

如果是后者:

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

因为这将在一次迭代中完成计数和转换,而无需建立新列表。


2
在Python 3中:list(map(bool, l)).count(True)

这只会找到文字True,而不是其他真实值(即:正整数而不是空容器,等等)
Matthew Scouten

6
只是向OP指出,当找到多个“ True”值时,这可能不会短路,因此它们的代码在某些情况下可能会提高效率。
NominSim 2013年

2
第二个函数可以写成return sum(bool(e) for e in l) == 1。关于算术,bool子类int和True / False的行为为1/0。

1
我会避免将其l用作变量名(看起来太像1此处),而我会改写sum(bool(e) for e in l)sum(1 for e in l if e)
wim

22

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

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。通过将其转换为列表并检查其长度是否等于一个,我们可以在发现两个真元素后,无需检查任何其他元素,就可以验证一个正真元素的存在。

以下是一些时间比较:

  • 设置代码:

    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()对于很小的列表,此方法会更快,但是随着输入列表的增大,即使无法短路,我的版本也会更快。当大输入可能发生短路时,性能差异显而易见。


5
哎哟。只要其他选项让我理解三次。如果短路很重要,我将采用OP的代码,因为它更明显,效率也差不多。

1
投票支持样式,并保留短路。但这很难阅读。
Matthew Scouten

1
+1。唯一能够再现OP短路意图的唯一器件。
NominSim

1
如果您提供一些timeit实验以与OP解决方案进行客观的性能比较,则+1 。
moooeeeep

@moooeeeep天真的,如果您有一个无限的可迭代项,并且True在“早于”某处具有两个值,则此操作将完成,而其他答案则永远旋转以获取计数。
NominSim 2013年

15

我想获得死灵法师的徽章,所以我概括了乔恩·克莱门茨的出色答案,保留了短路逻辑和快速谓词检查的好处。

因此,这里是:

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(true)> = 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)

11
>>> l = [0, 0, 1, 0, 0]
>>> has_one_true = len([ d for d in l if d ]) == 1
>>> has_one_true
True

4
为什么这被否决?我认为它是最简单,最易读的。
dansalmo 2013年

1
@dansalmo:当然很难确定,但是我的理论是,许多n00b python程序员(尤其是那些具有Java背景的人)对更长的语法感到更自在。(我自己,我曾经有点像5-10年前那样,但是今天我认为它是不专业和无知的。)+1
JonasByström17年

5

你可以做:

x = [bool(i) for i in x]
return x.count(True) == 1

要么

x = map(bool, x)
return x.count(True) == 1

以@JoranBeasley的方法为基础:

sum(map(bool, x)) == 1



4

这似乎可行,并且应该能够处理所有可迭代的对象,而不仅仅是lists。它会在可能的情况下短路以最大化效率。在Python 2和3中均可使用。

def only1(iterable):
    for i, x in enumerate(iterable):  # check each item in iterable
        if x: break                   # truthy value found
    else:
        return False                  # no truthy value found
    for x in iterable[i+1:]:          # one was found, see if there are any more
        if x: return False            #   found another...
    return True                       # only a single truthy value found

testcases = [  # [[iterable, expected result], ... ]
    [[                          ], False],
    [[False, False, False, False], False],
    [[True,  False, False, False], True],
    [[False, True,  False, False], True],
    [[False, False, False, True],  True],
    [[True,  False, True,  False], False],
    [[True,  True,  True,  True],  False],
]

for i, testcase in enumerate(testcases):
    correct = only1(testcase[0]) == testcase[1]
    print('only1(testcase[{}]): {}{}'.format(i, only1(testcase[0]),
                                             '' if correct else
                                             ', error given '+str(testcase[0])))

输出:

only1(testcase[0]): False
only1(testcase[1]): False
only1(testcase[2]): True
only1(testcase[3]): True
only1(testcase[4]): True
only1(testcase[5]): False
only1(testcase[6]): False

我喜欢这种方法,如何围绕返工的逻辑iter(x for x in my_list if x),然后使用next,可能比使用更好maplist.index
维姆

@wim:尽管我没有使用您建议的方法,但您的评论启发了我修改原始答案,并使其本质上更具增量,并摆脱了mapand list.index
martineau 2013年

3

@JonClements的解决方案最多扩展了N个True值

# Extend any() to n true values
def _NTrue(i, n=1):
    for x in xrange(n):
        if any(i): # False for empty
            continue
        else:
            return False
    return True

def NTrue(iterable, n=1):
    i = iter(iterable)
    return any(i) and not _NTrue(i, n)

编辑:更好的版本

def test(iterable, n=1): 
    i = iter(iterable) 
    return sum(any(i) for x in xrange(n+1)) <= n 

EDIT2:包括至少M个真实的最多n真正的

def test(iterable, n=1, m=1): 
    i = iter(iterable) 
    return  m <= sum(any(i) for x in xrange(n+1)) <= n

1
不,我的意思是最多。如果最多N次存在真实值的值,则返回true:如3个Trues在1000名单会得到iterable.count(True) = 3NTrue(iterable, 1) = FalseNTrue(iterable, 2) = FalseNTrue(iterable, 3) = TrueNTrue(iterable, 4) = True,...它基本上扩展and not any(i)部分and not any(i) and not any(i) and not...
Nisan.H

1
all(any(i) for i in xrange(n)) and not any(i)在这里不工作吗?
埃里克

@Eric只返回正好为n个true的True 。不过,它的确给了我一个对anys求和的想法。
Nisan.H 2013年

你是故意的any(i) and not all(any(i) for x in xrange(n))
moooeeeep

@moooeeeep在True and not all(<n booleans>)逻辑上不一样count(True) <= n吗?想法仍然是测试最小可能的设置并在第一个故障条件下中断。
Nisan.H 2013年

2
def only1(l)
    sum(map(lambda x: 1 if x else 0, l)) == 1

说明:该map函数将一个列表映射到另一个列表,True => 1并执行和False => 0。现在,我们有了一个0和1的列表,而不是True或False。现在,我们简单地对该列表求和,如果为1,则只有一个True值。


1

这是您要找的东西吗?

sum(l) == 1

对于列表,此操作失败:[2],因为作者未指定元素只能为True和False或1和0
vtlinh 2014年

1

为了完整起见,并演示了Python的控制流在for循环迭代中的高级用法,可以避免在已接受的答案中进行额外的核算,从而使其速度更快。

def one_bool_true(iterable):
    it = iter(iterable)
    for i in it:
        if i:
            break
    else:            #no break, didn't find a true element
        return False
    for i in it:     # continue consuming iterator where left off
        if i: 
            return False
    return True      # didn't find a second true.

上面的简单控制流程利用了Python复杂的循环功能:else。语义是,如果您在不消耗-的情况下完成正在使用的迭代器上的迭代,break则可以输入该else块。

这是公认的答案,它使用了更多的会计方法。

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

定时这些:

import timeit
>>> min(timeit.repeat(lambda: one_bool_true([0]*100 + [1, 1])))
13.992251592921093
>>> min(timeit.repeat(lambda: one_bool_true([1, 1] + [0]*100)))
2.208037032979064
>>> min(timeit.repeat(lambda: only1([0]*100 + [1, 1])))
14.213872335107908
>>> min(timeit.repeat(lambda: only1([1, 1] + [0]*100)))
2.2482982632641324
>>> 2.2482/2.2080
1.0182065217391305
>>> 14.2138/13.9922
1.0158373951201385

因此,我们看到可接受的答案花费的时间更长(略大于百分之一点半)。

自然地,使用any用C编写的内建函数要快得多(有关实现,请参见乔恩·克莱门特的答案-这是简写形式):

>>> min(timeit.repeat(lambda: single_true([0]*100 + [1, 1])))
2.7257133318785236
>>> min(timeit.repeat(lambda: single_true([1, 1] + [0]*100)))
2.012824866380015

0
import collections

def only_n(l, testval=True, n=1):
    counts = collections.Counter(l)
    return counts[testval] == n

线性时间。使用内置的Counter类,这是您应该用来检查计数的类。

重新阅读您的问题,看来您实际上想检查的是只有一个真实值,而不是一个True值。试试这个:

import collections

def only_n(l, testval=True, coerce=bool, n=1):
    counts = collections.Counter((coerce(x) for x in l))
    return counts[testval] == n

尽管可以获得更好的最佳情况性能,但没有任何情况可以提供更好的最坏情况性能。这也是简短易读的。

这是为实现最佳性能而优化的版本:

import collections
import itertools

def only_n(l, testval=True, coerce=bool, n=1):
    counts = collections.Counter()
    def iterate_and_count():
        for x in itertools.imap(coerce,l):
            yield x
            if x == testval and counts[testval] > n:
               break
    counts.update(iterate_and_count())
    return counts[testval] == n

最坏情况下的性能较高k(如所示O(kn+c)),但这是完全笼统的。

这是一个可以测试性能的ideone:http ://ideone.com/ZRrv2m


0

尽管没有短路,但这应该适用于任何真实情况。我在寻找一种禁止互相排斥的论点的干净方法时发现了它:

if sum(1 for item in somelist if item) != 1:
    raise ValueError("or whatever...")

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.